From 01dcca3363d2328b64c04cf7f4571790f7aff036 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 1 Jan 2024 17:21:00 +0300 Subject: [PATCH 01/47] Make scripted animations shut down pathfinding (bug #5065) --- CHANGELOG.md | 1 + apps/openmw/mwbase/mechanicsmanager.hpp | 2 ++ apps/openmw/mwmechanics/actors.cpp | 8 ++++++++ apps/openmw/mwmechanics/actors.hpp | 1 + apps/openmw/mwmechanics/aipackage.cpp | 11 ++++++----- apps/openmw/mwmechanics/character.hpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 8 ++++++++ apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 1 + 8 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd4e463f..0d9e199f2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Bug #4822: Non-weapon equipment and body parts can't inherit time from parent animation Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation + Bug #5065: Actors with scripted animation still try to wander and turn around without moving Bug #5066: Quirks with starting and stopping scripted animations Bug #5129: Stuttering animation on Centurion Archer Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index b8e0fd1bde..9e99a37ec7 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -187,6 +187,8 @@ namespace MWBase virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + virtual bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const = 0; + /// Save the current animation state of managed references to their RefData. virtual void persistAnimationStates() = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 25c4c97504..bc3cc3bb6a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2034,6 +2034,14 @@ namespace MWMechanics return false; } + bool Actors::checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const + { + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + return iter->second->getCharacterController().isScriptedAnimPlaying(); + return false; + } + void Actors::persistAnimationStates() const { for (const Actor& actor : mActors) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 15a39136a6..7c676ca018 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -116,6 +116,7 @@ namespace MWMechanics const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; void skipAnimation(const MWWorld::Ptr& ptr) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; + bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const; void persistAnimationStates() const; void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index a265c70cf4..fe83ce11ab 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -9,6 +9,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/luamanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" @@ -120,12 +121,12 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& MWBase::World* world = MWBase::Environment::get().getWorld(); const DetourNavigator::AgentBounds agentBounds = world->getPathfindingAgentBounds(actor); - /// Stops the actor when it gets too close to a unloaded cell - //... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" - // setting value - //... units from player, and exterior cells are 8192 units long and wide. + /// Stops the actor when it gets too close to a unloaded cell or when the actor is playing a scripted animation + //... At current time, the first test is unnecessary. AI shuts down when actor is more than + //... "actors processing range" setting value units from player, and exterior cells are 8192 units long and wide. //... But AI processing distance may increase in the future. - if (isNearInactiveCell(position)) + if (isNearInactiveCell(position) + || MWBase::Environment::get().getMechanicsManager()->checkScriptedAnimationPlaying(actor)) { actor.getClass().getMovementSettings(actor).mPosition[0] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index ee26b61a25..dc551900b5 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -217,7 +217,6 @@ namespace MWMechanics std::string chooseRandomAttackAnimation() const; static bool isRandomAttackAnimation(std::string_view group); - bool isScriptedAnimPlaying() const; bool isMovementAnimationControlled() const; void updateAnimQueue(); @@ -278,6 +277,7 @@ namespace MWMechanics bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; + bool isScriptedAnimPlaying() const; enum KillResult { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 59b1392dc9..c9e8e8f322 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -771,6 +771,14 @@ namespace MWMechanics return false; } + bool MechanicsManager::checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const + { + if (ptr.getClass().isActor()) + return mActors.checkScriptedAnimationPlaying(ptr); + + return false; + } + bool MechanicsManager::onOpen(const MWWorld::Ptr& ptr) { if (ptr.getClass().isActor()) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 997636522e..f2483396fe 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -145,6 +145,7 @@ namespace MWMechanics const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; void skipAnimation(const MWWorld::Ptr& ptr) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; + bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override; void persistAnimationStates() override; /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently From 1f26485c478e901978124166fb1e1e796c58ceeb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 6 Jan 2024 05:18:30 +0300 Subject: [PATCH 02/47] Fix exterior sun direction/position (bug #4898) --- CHANGELOG.md | 1 + apps/openmw/mwrender/renderingmanager.cpp | 3 +++ apps/openmw/mwworld/weather.cpp | 16 ++++++++-------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd4e463f..1771d0ab81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #4822: Non-weapon equipment and body parts can't inherit time from parent animation + Bug #4898: Odd/Incorrect lighting on meshes Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation Bug #5066: Quirks with starting and stopping scripted animations diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index fa92fa1420..2e3698a197 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -716,6 +716,9 @@ namespace MWRender // need to wrap this in a StateUpdater? mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0)); + // The sun is not synchronized with the sunlight because sunlight origin can't reach the horizon + // This is based on exterior sun orbit and won't make sense for interiors, see WeatherManager::update + position.z() = 400.f - std::abs(position.x()); mSky->setSunDirection(position); mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index aa75730b40..584f73520c 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -752,21 +752,21 @@ namespace MWWorld const float dayDuration = adjustedNightStart - mSunriseTime; const float nightDuration = 24.f - dayDuration; - double theta; + float orbit; if (!is_night) { - theta = static_cast(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; + float t = (adjustedHour - mSunriseTime) / dayDuration; + orbit = 1.f - 2.f * t; } else { - theta = static_cast(osg::PI) - - static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; + float t = (adjustedHour - adjustedNightStart) / nightDuration; + orbit = 2.f * t - 1.f; } - osg::Vec3f final(static_cast(cos(theta)), - -0.268f, // approx tan( -15 degrees ) - static_cast(sin(theta))); - mRendering.setSunDirection(final * -1); + // Hardcoded constant from Morrowind + const osg::Vec3f sunDir(-400.f * orbit, 75.f, -100.f); + mRendering.setSunDirection(sunDir); mRendering.setNight(is_night); } From 9b8d6855784da859e77dc0ebc8d7c2c361f2e761 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 6 Jan 2024 21:58:12 +0300 Subject: [PATCH 03/47] Expose requested apparent sun position (not normalized) to post-processing --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2e3698a197..c2c6abd1bc 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -721,7 +721,7 @@ namespace MWRender position.z() = 400.f - std::abs(position.x()); mSky->setSunDirection(position); - mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight); + mPostProcessor->getStateUpdater()->setSunPos(osg::Vec4f(position, 0.f), mNight); } void RenderingManager::addCell(const MWWorld::CellStore* store) From 69cf507db85845446b394129dc8955f3803dfde0 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 8 Jan 2024 22:17:02 +0100 Subject: [PATCH 04/47] Fix navmesh update on player changing tile In cases when objects are not present on the scene (e.g. generated exterior cells) navmesh is not updated because area that suppose to be covered with it was not updated. It was updated only during cell change. This is a regression from d15e1dca84. Set TileCachedRecastMeshManager range on NavMeshManager update to make sure it always covers correct area around player. Return a union of objects, heightfields and water ranges from getLimitedObjectsRange intersected with range provided above. --- .../detournavigator/gettilespositions.cpp | 76 +++++++ .../detournavigator/navigator.cpp | 185 +++++++++++++++--- .../detournavigator/operators.hpp | 18 ++ .../detournavigator/gettilespositions.cpp | 9 + .../detournavigator/gettilespositions.hpp | 2 + components/detournavigator/navmeshmanager.cpp | 1 + .../tilecachedrecastmeshmanager.cpp | 39 +++- 7 files changed, 299 insertions(+), 31 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp index fb32022010..729d11ddb5 100644 --- a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp +++ b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -86,4 +87,79 @@ namespace EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } + + struct TilesPositionsRangeParams + { + TilesPositionsRange mA; + TilesPositionsRange mB; + TilesPositionsRange mResult; + }; + + struct DetourNavigatorGetIntersectionTest : TestWithParam + { + }; + + TEST_P(DetourNavigatorGetIntersectionTest, should_return_expected_result) + { + EXPECT_EQ(getIntersection(GetParam().mA, GetParam().mB), GetParam().mResult); + EXPECT_EQ(getIntersection(GetParam().mB, GetParam().mA), GetParam().mResult); + } + + const TilesPositionsRangeParams getIntersectionParams[] = { + { .mA = TilesPositionsRange{}, .mB = TilesPositionsRange{}, .mResult = TilesPositionsRange{} }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 2, 2 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 3, 3 } }, + .mResult = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 2, 2 } }, + }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 2, 2 }, .mEnd = TilePosition{ 3, 3 } }, + .mResult = TilesPositionsRange{}, + }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 2, 2 } }, + .mResult = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 1, 1 } }, + }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 0, 2 }, .mEnd = TilePosition{ 3, 3 } }, + .mResult = TilesPositionsRange{}, + }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 2, 0 }, .mEnd = TilePosition{ 3, 3 } }, + .mResult = TilesPositionsRange{}, + }, + }; + + INSTANTIATE_TEST_SUITE_P( + GetIntersectionParams, DetourNavigatorGetIntersectionTest, ValuesIn(getIntersectionParams)); + + struct DetourNavigatorGetUnionTest : TestWithParam + { + }; + + TEST_P(DetourNavigatorGetUnionTest, should_return_expected_result) + { + EXPECT_EQ(getUnion(GetParam().mA, GetParam().mB), GetParam().mResult); + EXPECT_EQ(getUnion(GetParam().mB, GetParam().mA), GetParam().mResult); + } + + const TilesPositionsRangeParams getUnionParams[] = { + { .mA = TilesPositionsRange{}, .mB = TilesPositionsRange{}, .mResult = TilesPositionsRange{} }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 2, 2 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 3, 3 } }, + .mResult = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 3, 3 } }, + }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 2, 2 } }, + .mResult = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 2, 2 } }, + }, + }; + + INSTANTIATE_TEST_SUITE_P(GetUnionParams, DetourNavigatorGetUnionTest, ValuesIn(getUnionParams)); } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index df4d7a1e99..aba8598f18 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -39,6 +39,8 @@ namespace using namespace DetourNavigator; using namespace DetourNavigator::Tests; + constexpr int heightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); + struct DetourNavigatorNavigatorTest : Test { Settings mSettings = makeSettings(); @@ -53,7 +55,6 @@ namespace AreaCosts mAreaCosts; Loading::Listener mListener; const osg::Vec2i mCellPosition{ 0, 0 }; - const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); const float mEndTolerance = 0; const btTransform mTransform{ btMatrix3x3::getIdentity(), btVector3(256, 256, 0) }; const ObjectTransform mObjectTransform{ ESM::Position{ { 256, 256, 0 }, { 0, 0, 0 } }, 0.0f }; @@ -129,7 +130,7 @@ namespace { } - T& shape() { return static_cast(*mInstance->mCollisionShape); } + T& shape() const { return static_cast(*mInstance->mCollisionShape); } const osg::ref_ptr& instance() const { return mInstance; } private: @@ -167,7 +168,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); auto updateGuard = mNavigator->makeUpdateGuard(); @@ -189,7 +190,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_to_the_start_position_should_contain_single_point) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); auto updateGuard = mNavigator->makeUpdateGuard(); @@ -211,7 +212,7 @@ namespace mSettings, std::make_unique(":memory:", std::numeric_limits::max()))); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape( @@ -256,7 +257,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_changed_object_should_change_navmesh) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape( @@ -335,7 +336,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, only_one_heightfield_per_cell_is_allowed) { const HeightfieldSurface surface1 = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize1 = mHeightfieldTileSize * (surface1.mSize - 1); + const int cellSize1 = heightfieldTileSize * (surface1.mSize - 1); const std::array heightfieldData2{ { -25, -25, -25, -25, -25, // row 0 @@ -345,7 +346,7 @@ namespace -25, -25, -25, -25, -25, // row 4 } }; const HeightfieldSurface surface2 = makeSquareHeightfieldSurface(heightfieldData2); - const int cellSize2 = mHeightfieldTileSize * (surface2.mSize - 1); + const int cellSize2 = heightfieldTileSize * (surface2.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize1, surface1, nullptr); @@ -412,7 +413,7 @@ namespace 0, -50, -100, -100, -100, // row 4 } }; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addWater(mCellPosition, cellSize, 300, nullptr); @@ -446,7 +447,7 @@ namespace 0, 0, 0, 0, 0, 0, 0, // row 6 } }; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addWater(mCellPosition, cellSize, -25, nullptr); @@ -480,7 +481,7 @@ namespace 0, 0, 0, 0, 0, 0, 0, // row 6 } }; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); @@ -513,7 +514,7 @@ namespace 0, 0, 0, 0, 0, 0, 0, // row 6 } }; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addWater(mCellPosition, cellSize, -25, nullptr); @@ -566,7 +567,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_heightfield_remove_and_update_then_find_path_should_return_path) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); @@ -602,7 +603,7 @@ namespace 0, -25, -100, -100, -100, -100, // row 5 } }; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); @@ -629,7 +630,7 @@ namespace mSettings, std::make_unique(":memory:", std::numeric_limits::max()))); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); const btVector3 shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); std::vector> boxes; @@ -718,7 +719,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); @@ -737,7 +738,7 @@ namespace update_for_oscillating_object_that_does_not_change_navmesh_should_not_trigger_navmesh_update) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); CollisionShapeInstance oscillatingBox(std::make_unique(btVector3(20, 20, 20))); const btVector3 oscillatingBoxShapePosition(288, 288, 400); @@ -777,7 +778,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, should_provide_path_over_flat_heightfield) { const HeightfieldPlane plane{ 100 }; - const int cellSize = mHeightfieldTileSize * 4; + const int cellSize = heightfieldTileSize * 4; ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize, plane, nullptr); @@ -796,7 +797,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, for_not_reachable_destination_find_path_should_provide_partial_path) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), @@ -822,7 +823,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, end_tolerance_should_extent_available_destinations) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), @@ -948,7 +949,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nav_mesh_position) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); auto updateGuard = mNavigator->makeUpdateGuard(); @@ -966,7 +967,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_too_far) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); auto updateGuard = mNavigator->makeUpdateGuard(); @@ -984,7 +985,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_flags_do_not_match) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); auto updateGuard = mNavigator->makeUpdateGuard(); @@ -998,4 +999,142 @@ namespace EXPECT_EQ(findNearestNavMeshPosition(*mNavigator, mAgentBounds, position, searchAreaHalfExtents, Flag_swim), std::nullopt); } + + struct DetourNavigatorUpdateTest : TestWithParam> + { + }; + + std::vector getUsedTiles(const NavMeshCacheItem& navMesh) + { + std::vector result; + navMesh.forEachUsedTile([&](const TilePosition& position, const auto&...) { result.push_back(position); }); + return result; + } + + TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves) + { + Loading::Listener listener; + Settings settings = makeSettings(); + settings.mMaxTilesNumber = 5; + NavigatorImpl navigator(settings, nullptr); + const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } }; + ASSERT_TRUE(navigator.addAgent(agentBounds)); + + GetParam()(navigator); + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::allJobsDone, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTiles[] = { { 3, 4 }, { 4, 3 }, { 4, 4 }, { 4, 5 }, { 5, 4 } }; + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, UnorderedElementsAreArray(expectedTiles)) << usedTiles; + } + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(4000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::allJobsDone, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTiles[] = { { 4, 4 }, { 5, 3 }, { 5, 4 }, { 5, 5 }, { 6, 4 } }; + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, UnorderedElementsAreArray(expectedTiles)) << usedTiles; + } + } + + struct AddHeightfieldSurface + { + static constexpr std::size_t sSize = 65; + static constexpr float sHeights[sSize * sSize]{}; + + void operator()(Navigator& navigator) const + { + const osg::Vec2i cellPosition(0, 0); + const HeightfieldSurface surface{ + .mHeights = sHeights, + .mSize = sSize, + .mMinHeight = -1, + .mMaxHeight = 1, + }; + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); + navigator.addHeightfield(cellPosition, cellSize, surface, nullptr); + } + }; + + struct AddHeightfieldPlane + { + void operator()(Navigator& navigator) const + { + const osg::Vec2i cellPosition(0, 0); + const HeightfieldPlane plane{ .mHeight = 0 }; + const int cellSize = 8192; + navigator.addHeightfield(cellPosition, cellSize, plane, nullptr); + } + }; + + struct AddWater + { + void operator()(Navigator& navigator) const + { + const osg::Vec2i cellPosition(0, 0); + const float level = 0; + const int cellSize = 8192; + navigator.addWater(cellPosition, cellSize, level, nullptr); + } + }; + + struct AddObject + { + const float mSize = 8192; + CollisionShapeInstance mBox{ std::make_unique(btVector3(mSize, mSize, 1)) }; + const ObjectTransform mTransform{ + .mPosition = ESM::Position{ .pos = { 0, 0, 0 }, .rot{ 0, 0, 0 } }, + .mScale = 1.0f, + }; + + void operator()(Navigator& navigator) const + { + navigator.addObject(ObjectId(&mBox.shape()), ObjectShapes(mBox.instance(), mTransform), + btTransform::getIdentity(), nullptr); + } + }; + + struct AddAll + { + AddHeightfieldSurface mAddHeightfieldSurface; + AddHeightfieldPlane mAddHeightfieldPlane; + AddWater mAddWater; + AddObject mAddObject; + + void operator()(Navigator& navigator) const + { + mAddHeightfieldSurface(navigator); + mAddHeightfieldPlane(navigator); + mAddWater(navigator); + mAddObject(navigator); + } + }; + + const std::function addNavMeshData[] = { + AddHeightfieldSurface{}, + AddHeightfieldPlane{}, + AddWater{}, + AddObject{}, + AddAll{}, + }; + + INSTANTIATE_TEST_SUITE_P(DifferentNavMeshData, DetourNavigatorUpdateTest, ValuesIn(addNavMeshData)); } diff --git a/apps/openmw_test_suite/detournavigator/operators.hpp b/apps/openmw_test_suite/detournavigator/operators.hpp index 4e42af78e4..4c043027eb 100644 --- a/apps/openmw_test_suite/detournavigator/operators.hpp +++ b/apps/openmw_test_suite/detournavigator/operators.hpp @@ -42,12 +42,24 @@ namespace testing << ", " << value.y() << ", " << value.z() << ')'; } + template <> + inline testing::Message& Message::operator<<(const osg::Vec2i& value) + { + return (*this) << "{" << value.x() << ", " << value.y() << '}'; + } + template <> inline testing::Message& Message::operator<<(const Wrapper& value) { return (*this) << value.mValue; } + template <> + inline testing::Message& Message::operator<<(const Wrapper& value) + { + return (*this) << value.mValue; + } + template <> inline testing::Message& Message::operator<<(const Wrapper& value) { @@ -72,6 +84,12 @@ namespace testing return writeRange(*this, value, 1); } + template <> + inline testing::Message& Message::operator<<(const std::vector& value) + { + return writeRange(*this, value, 1); + } + template <> inline testing::Message& Message::operator<<(const std::vector& value) { diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp index a3f46f3f85..343140633f 100644 --- a/components/detournavigator/gettilespositions.cpp +++ b/components/detournavigator/gettilespositions.cpp @@ -76,4 +76,13 @@ namespace DetourNavigator return {}; return TilesPositionsRange{ TilePosition(beginX, beginY), TilePosition(endX, endY) }; } + + TilesPositionsRange getUnion(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept + { + const int beginX = std::min(a.mBegin.x(), b.mBegin.x()); + const int endX = std::max(a.mEnd.x(), b.mEnd.x()); + const int beginY = std::min(a.mBegin.y(), b.mBegin.y()); + const int endY = std::max(a.mEnd.y(), b.mEnd.y()); + return TilesPositionsRange{ .mBegin = TilePosition(beginX, beginY), .mEnd = TilePosition(endX, endY) }; + } } diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 32733224f3..66c3a90d1b 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -50,6 +50,8 @@ namespace DetourNavigator } TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept; + + TilesPositionsRange getUnion(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept; } #endif diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 9fda0566d9..f4a82b850f 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -166,6 +166,7 @@ namespace DetourNavigator return; mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision(); mPlayerTile = playerTile; + mRecastMeshManager.setRange(makeRange(playerTile, mSettings.mMaxTilesNumber), guard); const auto changedTiles = mRecastMeshManager.takeChangedTiles(guard); const TilesPositionsRange range = mRecastMeshManager.getLimitedObjectsRange(); for (const auto& [agentBounds, cached] : mCache) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 1e55719c13..0bab808300 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -54,6 +54,15 @@ namespace DetourNavigator private: const std::optional> mImpl; }; + + TilesPositionsRange getIndexRange(const auto& index) + { + const auto bounds = index.bounds(); + return TilesPositionsRange{ + .mBegin = makeTilePosition(bounds.min_corner()), + .mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1), + }; + } } TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) @@ -104,14 +113,28 @@ namespace DetourNavigator TilesPositionsRange TileCachedRecastMeshManager::getLimitedObjectsRange() const { - if (mObjects.empty()) - return {}; - const auto bounds = mObjectIndex.bounds(); - const TilesPositionsRange objectsRange{ - .mBegin = makeTilePosition(bounds.min_corner()), - .mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1), - }; - return getIntersection(mRange, objectsRange); + std::optional result; + if (!mWater.empty()) + result = getIndexRange(mWaterIndex); + if (!mHeightfields.empty()) + { + const TilesPositionsRange range = getIndexRange(mHeightfieldIndex); + if (result.has_value()) + result = getUnion(*result, range); + else + result = range; + } + if (!mObjects.empty()) + { + const TilesPositionsRange range = getIndexRange(mObjectIndex); + if (result.has_value()) + result = getUnion(*result, range); + else + result = range; + } + if (result.has_value()) + return getIntersection(mRange, *result); + return {}; } void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace, const UpdateGuard* guard) From c4ed812567b1ac7b8b25bd962794e8ce000cbcd2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 11 Jan 2024 03:12:13 +0300 Subject: [PATCH 05/47] Properly redraw the topics list when disposition bar state changes --- apps/openmw/mwgui/dialogue.cpp | 32 ++++++++++++++------------------ apps/openmw/mwgui/dialogue.hpp | 1 + 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 0e44b8c03e..ce79e2834c 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -364,9 +364,8 @@ namespace MWGui if (mCurrentWindowSize == _sender->getSize()) return; - mTopicsList->adjustSize(); + redrawTopicsList(); updateHistory(); - updateTopicFormat(); mCurrentWindowSize = _sender->getSize(); } @@ -534,6 +533,14 @@ namespace MWGui return true; } + void DialogueWindow::redrawTopicsList() + { + mTopicsList->adjustSize(); + + // The topics list has been regenerated so topic formatting needs to be updated + updateTopicFormat(); + } + void DialogueWindow::updateTopicsPane() { mTopicsList->clear(); @@ -591,11 +598,9 @@ namespace MWGui t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated); mTopicLinks[topicId] = std::move(t); } - mTopicsList->adjustSize(); + redrawTopicsList(); updateHistory(); - // The topics list has been regenerated so topic formatting needs to be updated - updateTopicFormat(); } void DialogueWindow::updateHistory(bool scrollbar) @@ -756,21 +761,12 @@ namespace MWGui + std::string("/100")); } - bool dispositionWasVisible = mDispositionBar->getVisible(); - - if (dispositionVisible && !dispositionWasVisible) + if (mDispositionBar->getVisible() != dispositionVisible) { - mDispositionBar->setVisible(true); - int offset = mDispositionBar->getHeight() + 5; + mDispositionBar->setVisible(dispositionVisible); + const int offset = (mDispositionBar->getHeight() + 5) * (dispositionVisible ? 1 : -1); mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0, offset, 0, -offset)); - mTopicsList->adjustSize(); - } - else if (!dispositionVisible && dispositionWasVisible) - { - mDispositionBar->setVisible(false); - int offset = mDispositionBar->getHeight() + 5; - mTopicsList->setCoord(mTopicsList->getCoord() - MyGUI::IntCoord(0, offset, 0, -offset)); - mTopicsList->adjustSize(); + redrawTopicsList(); } } diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 1b79cadca5..8a8b309401 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -190,6 +190,7 @@ namespace MWGui void updateDisposition(); void restock(); void deleteLater(); + void redrawTopicsList(); bool mIsCompanion; std::list mKeywords; From 1880894f4ad943b05561226b7f4ec6889efdb299 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 11 Jan 2024 19:05:37 +0100 Subject: [PATCH 06/47] Use ciEqual to detect missing content files --- apps/openmw/mwstate/statemanagerimp.cpp | 5 ++--- components/esm3/savedgame.cpp | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 4358c4094e..631ef9a112 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -81,10 +82,8 @@ std::map MWState::StateManager::buildContentFileIndexMap(const ESM::ES for (int iPrev = 0; iPrev < static_cast(prev.size()); ++iPrev) { - std::string id = Misc::StringUtils::lowerCase(prev[iPrev].name); - for (int iCurrent = 0; iCurrent < static_cast(current.size()); ++iCurrent) - if (id == Misc::StringUtils::lowerCase(current[iCurrent])) + if (Misc::StringUtils::ciEqual(prev[iPrev].name, current[iCurrent])) { map.insert(std::make_pair(iPrev, iCurrent)); break; diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 3ffe062d76..0dc1fb0653 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -3,6 +3,8 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "../misc/algorithm.hpp" + namespace ESM { void SavedGame::load(ESMReader& esm) @@ -67,7 +69,9 @@ namespace ESM std::vector missingFiles; for (const std::string& contentFile : mContentFiles) { - if (std::find(allContentFiles.begin(), allContentFiles.end(), contentFile) == allContentFiles.end()) + auto it = std::find_if(allContentFiles.begin(), allContentFiles.end(), + [&](const std::string& file) { return Misc::StringUtils::ciEqual(file, contentFile); }); + if (it == allContentFiles.end()) { missingFiles.emplace_back(contentFile); } From 3ad79e3b3eb7d254128cc43696ca451940201ff6 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Dec 2023 12:16:14 +0100 Subject: [PATCH 07/47] Pregenerate glow texture names To avoid strings generation and allocations every time model is added to a scene. --- components/sceneutil/util.cpp | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 6ff366f76e..b71de4c2a3 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -1,6 +1,7 @@ #include "util.hpp" #include +#include #include #include @@ -17,6 +18,26 @@ namespace SceneUtil { + namespace + { + std::array generateGlowTextureNames() + { + std::array result; + for (std::size_t i = 0; i < result.size(); ++i) + { + std::stringstream stream; + stream << "textures/magicitem/caust"; + stream << std::setw(2); + stream << std::setfill('0'); + stream << i; + stream << ".dds"; + result[i] = std::move(stream).str(); + } + return result; + } + + const std::array glowTextureNames = generateGlowTextureNames(); + } class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor { @@ -197,16 +218,9 @@ namespace SceneUtil const osg::Vec4f& glowColor, float glowDuration) { std::vector> textures; - for (int i = 0; i < 32; ++i) + for (const std::string& name : glowTextureNames) { - std::stringstream stream; - stream << "textures/magicitem/caust"; - stream << std::setw(2); - stream << std::setfill('0'); - stream << i; - stream << ".dds"; - - osg::ref_ptr image = resourceSystem->getImageManager()->getImage(stream.str()); + osg::ref_ptr image = resourceSystem->getImageManager()->getImage(name); osg::ref_ptr tex(new osg::Texture2D(image)); tex->setName("envMap"); tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); From 35da9f8c50325331a674b45445809b68be994933 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Jan 2024 01:49:17 +0100 Subject: [PATCH 08/47] Remove redundant SizeProxy and RenderTarget constructors --- components/fx/types.hpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/components/fx/types.hpp b/components/fx/types.hpp index 0f33d29e1a..829bf176b7 100644 --- a/components/fx/types.hpp +++ b/components/fx/types.hpp @@ -29,16 +29,6 @@ namespace fx std::optional mWidth; std::optional mHeight; - SizeProxy() = default; - - SizeProxy(const SizeProxy& other) - : mWidthRatio(other.mWidthRatio) - , mHeightRatio(other.mHeightRatio) - , mWidth(other.mWidth) - , mHeight(other.mHeight) - { - } - std::tuple get(int width, int height) const { int scaledWidth = width; @@ -64,16 +54,6 @@ namespace fx SizeProxy mSize; bool mMipMap = false; osg::Vec4f mClearColor = osg::Vec4f(0.0, 0.0, 0.0, 1.0); - - RenderTarget() = default; - - RenderTarget(const RenderTarget& other) - : mTarget(other.mTarget) - , mSize(other.mSize) - , mMipMap(other.mMipMap) - , mClearColor(other.mClearColor) - { - } }; template From 1bfcfaff341f65666fb9d9f774f9baeae1e425ce Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Jan 2024 03:35:57 +0100 Subject: [PATCH 09/47] Use proper naming for member variable --- components/esm3/aisequence.cpp | 2 +- components/esm3/aisequence.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index 99c85db1bb..d5b15893bf 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -14,7 +14,7 @@ namespace ESM void AiWander::load(ESMReader& esm) { esm.getHNT("DATA", mData.mDistance, mData.mDuration, mData.mTimeOfDay, mData.mIdle, mData.mShouldRepeat); - esm.getHNT("STAR", mDurationData.mRemainingDuration, mDurationData.unused); // was mStartTime + esm.getHNT("STAR", mDurationData.mRemainingDuration, mDurationData.mUnused); // was mStartTime mStoredInitialActorPosition = esm.getHNOT("POS_", mInitialActorPosition.mValues); } diff --git a/components/esm3/aisequence.hpp b/components/esm3/aisequence.hpp index 107fdf3bdb..099e5560e1 100644 --- a/components/esm3/aisequence.hpp +++ b/components/esm3/aisequence.hpp @@ -48,7 +48,7 @@ namespace ESM struct AiWanderDuration { float mRemainingDuration; - int32_t unused; + int32_t mUnused; }; struct AiTravelData { From 074ab682abc31b7f844da6cad79818a60ce016e5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 12 Jan 2024 10:04:56 +0400 Subject: [PATCH 10/47] Move local variables in the editor --- apps/opencs/model/world/data.cpp | 4 ++-- apps/opencs/model/world/idcompletionmanager.cpp | 2 +- apps/opencs/view/doc/adjusterwidget.cpp | 2 +- apps/opencs/view/doc/viewmanager.cpp | 2 +- apps/opencs/view/render/pagedworldspacewidget.cpp | 2 +- apps/opencs/view/render/terraintexturemode.cpp | 4 ++-- apps/opencs/view/widget/scenetooltexturebrush.cpp | 2 +- apps/opencs/view/world/extendedcommandconfigurator.cpp | 2 +- apps/opencs/view/world/tablesubview.cpp | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 6322a77e66..601ee8d263 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1107,7 +1107,7 @@ void CSMWorld::Data::loadFallbackEntries() newMarker.mModel = model; newMarker.mRecordFlags = 0; auto record = std::make_unique>(); - record->mBase = newMarker; + record->mBase = std::move(newMarker); record->mState = CSMWorld::RecordBase::State_BaseOnly; mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Static); } @@ -1123,7 +1123,7 @@ void CSMWorld::Data::loadFallbackEntries() newMarker.mModel = model; newMarker.mRecordFlags = 0; auto record = std::make_unique>(); - record->mBase = newMarker; + record->mBase = std::move(newMarker); record->mState = CSMWorld::RecordBase::State_BaseOnly; mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Door); } diff --git a/apps/opencs/model/world/idcompletionmanager.cpp b/apps/opencs/model/world/idcompletionmanager.cpp index 263f462b6e..a4fdb4776d 100644 --- a/apps/opencs/model/world/idcompletionmanager.cpp +++ b/apps/opencs/model/world/idcompletionmanager.cpp @@ -117,7 +117,7 @@ void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data& data) completer->setPopup(popup); // The completer takes ownership of the popup completer->setMaxVisibleItems(10); - mCompleters[current->first] = completer; + mCompleters[current->first] = std::move(completer); } } } diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index d4cfdc6d3e..a282ebcaff 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -91,7 +91,7 @@ void CSVDoc::AdjusterWidget::setName(const QString& name, bool addon) { // path already points to the local data directory message = "Will be saved as: " + Files::pathToQString(path); - mResultPath = path; + mResultPath = std::move(path); } // in all other cases, ensure the path points to data-local and do an existing file check else diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index dff4426ba5..812a1bd534 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -410,7 +410,7 @@ bool CSVDoc::ViewManager::removeDocument(CSVDoc::View* view) remainingViews.push_back(*iter); } mDocumentManager.removeDocument(document); - mViews = remainingViews; + mViews = std::move(remainingViews); } return true; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 3d5c6fe565..214618a627 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -514,7 +514,7 @@ void CSVRender::PagedWorldspaceWidget::moveCellSelection(int x, int y) addCellToScene(*iter); } - mSelection = newSelection; + mSelection = std::move(newSelection); } void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera(int offsetX, int offsetY) diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index 79e9959cd6..684958da34 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -541,7 +541,7 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe = landTable.data(landTable.getModelIndex(cellId, textureColumn)) .value(); newTerrainOtherCell[yInOtherCell * landTextureSize + xInOtherCell] = brushInt; - pushEditToCommand(newTerrainOtherCell, document, landTable, cellId); + pushEditToCommand(newTerrainOtherCell, document, landTable, std::move(cellId)); } } } @@ -702,7 +702,7 @@ void CSVRender::TerrainTextureMode::createTexture(const std::string& textureFile QModelIndex index(ltexTable.getModelIndex(newId, ltexTable.findColumnIndex(CSMWorld::Columns::ColumnId_Texture))); undoStack.push(new CSMWorld::ModifyCommand(ltexTable, index, textureFileNameVariant)); undoStack.endMacro(); - mBrushTexture = newId; + mBrushTexture = std::move(newId); } bool CSVRender::TerrainTextureMode::allowLandTextureEditing(const std::string& cellId) diff --git a/apps/opencs/view/widget/scenetooltexturebrush.cpp b/apps/opencs/view/widget/scenetooltexturebrush.cpp index cc372753f6..9c3e723009 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.cpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.cpp @@ -213,7 +213,7 @@ void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture) mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel)); } - mBrushTexture = newBrushTextureId; + mBrushTexture = std::move(newBrushTextureId); emit passTextureId(mBrushTexture); emit passBrushShape(mBrushShape); // updates the icon tooltip diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index 69659be8a6..97494fa076 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -146,7 +146,7 @@ void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vectorfirst->setText(QString::fromUtf8(type.getTypeName().c_str())); current->first->setChecked(true); - current->second = type; + current->second = std::move(type); ++counter; } else diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index c9e09e2d6a..1d4dc37529 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -169,7 +169,7 @@ void CSVWorld::TableSubView::createFilterRequest(std::vectorgetId(); - filterData.columns = col; + filterData.columns = std::move(col); sourceFilter.emplace_back(filterData); } From 3592dc4c8835d49a076007296714019a9ec83230 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Jan 2024 03:01:49 +0100 Subject: [PATCH 11/47] Add tests for saving and loading AiSequence::AiWander --- apps/openmw_test_suite/esm3/testsaveload.cpp | 82 ++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index ff68d0d4f1..501a0b47c0 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -410,6 +411,87 @@ namespace ESM EXPECT_EQ(result.mStringId, record.mStringId); } + TEST_P(Esm3SaveLoadRecordTest, aiSequenceAiWanderShouldNotChange) + { + AiSequence::AiWander record; + record.mData.mDistance = 1; + record.mData.mDuration = 2; + record.mData.mTimeOfDay = 3; + constexpr std::uint8_t idle[8] = { 4, 5, 6, 7, 8, 9, 10, 11 }; + static_assert(std::size(idle) == std::size(record.mData.mIdle)); + std::copy(std::begin(idle), std::end(idle), record.mData.mIdle); + record.mData.mShouldRepeat = 12; + record.mDurationData.mRemainingDuration = 13; + record.mDurationData.mUnused = 14; + record.mStoredInitialActorPosition = true; + constexpr float initialActorPosition[3] = { 15, 16, 17 }; + static_assert(std::size(initialActorPosition) == std::size(record.mInitialActorPosition.mValues)); + std::copy( + std::begin(initialActorPosition), std::end(initialActorPosition), record.mInitialActorPosition.mValues); + + AiSequence::AiWander result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mDistance, record.mData.mDistance); + EXPECT_EQ(result.mData.mDuration, record.mData.mDuration); + EXPECT_EQ(result.mData.mTimeOfDay, record.mData.mTimeOfDay); + EXPECT_THAT(result.mData.mIdle, ElementsAreArray(record.mData.mIdle)); + EXPECT_EQ(result.mData.mShouldRepeat, record.mData.mShouldRepeat); + EXPECT_EQ(result.mDurationData.mRemainingDuration, record.mDurationData.mRemainingDuration); + EXPECT_EQ(result.mDurationData.mUnused, record.mDurationData.mUnused); + EXPECT_EQ(result.mStoredInitialActorPosition, record.mStoredInitialActorPosition); + EXPECT_THAT(result.mInitialActorPosition.mValues, ElementsAreArray(record.mInitialActorPosition.mValues)); + } + + TEST_P(Esm3SaveLoadRecordTest, aiSequenceAiTravelShouldNotChange) + { + AiSequence::AiTravel record; + record.mData.mX = 1; + record.mData.mY = 2; + record.mData.mZ = 3; + record.mHidden = true; + record.mRepeat = true; + + AiSequence::AiTravel result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mX, record.mData.mX); + EXPECT_EQ(result.mData.mY, record.mData.mY); + EXPECT_EQ(result.mData.mZ, record.mData.mZ); + EXPECT_EQ(result.mHidden, record.mHidden); + EXPECT_EQ(result.mRepeat, record.mRepeat); + } + + TEST_P(Esm3SaveLoadRecordTest, aiSequenceAiEscortShouldNotChange) + { + AiSequence::AiEscort record; + record.mData.mX = 1; + record.mData.mY = 2; + record.mData.mZ = 3; + record.mData.mDuration = 4; + record.mTargetActorId = 5; + record.mTargetId = generateRandomRefId(32); + record.mCellId = generateRandomString(257); + record.mRemainingDuration = 6; + record.mRepeat = true; + + AiSequence::AiEscort result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mX, record.mData.mX); + EXPECT_EQ(result.mData.mY, record.mData.mY); + EXPECT_EQ(result.mData.mZ, record.mData.mZ); + if (GetParam() <= MaxOldAiPackageFormatVersion) + EXPECT_EQ(result.mData.mDuration, record.mRemainingDuration); + else + EXPECT_EQ(result.mData.mDuration, record.mData.mDuration); + EXPECT_EQ(result.mTargetActorId, record.mTargetActorId); + EXPECT_EQ(result.mTargetId, record.mTargetId); + EXPECT_EQ(result.mCellId, record.mCellId); + EXPECT_EQ(result.mRemainingDuration, record.mRemainingDuration); + EXPECT_EQ(result.mRepeat, record.mRepeat); + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } From dd706aab0e118fdd185615558e6fc1b9fdf6e9e2 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Jan 2024 23:01:09 +0100 Subject: [PATCH 12/47] Add missing SubPass::mMinMap initialization --- components/fx/technique.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index 0d17128e56..fa66996aeb 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -55,7 +55,7 @@ namespace fx osg::ref_ptr mRenderTexture; bool mResolve = false; Types::SizeProxy mSize; - bool mMipMap; + bool mMipMap = false; SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) : mStateSet(new osg::StateSet(*other.mStateSet, copyOp)) From 384a1dd13acbbaaa91f35bafed473b9cbb6799f3 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 13 Jan 2024 00:44:07 +0100 Subject: [PATCH 13/47] Update PrecipitationOccluder only when there is precipitation --- apps/openmw/mwrender/precipitationocclusion.cpp | 17 +++++++++++++---- apps/openmw/mwrender/precipitationocclusion.hpp | 4 +++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/precipitationocclusion.cpp b/apps/openmw/mwrender/precipitationocclusion.cpp index 204b5c07bd..31712410b8 100644 --- a/apps/openmw/mwrender/precipitationocclusion.cpp +++ b/apps/openmw/mwrender/precipitationocclusion.cpp @@ -1,5 +1,7 @@ #include "precipitationocclusion.hpp" +#include + #include #include @@ -120,16 +122,19 @@ namespace MWRender void PrecipitationOccluder::update() { + if (!mRange.has_value()) + return; + const osg::Vec3 pos = mSceneCamera->getInverseViewMatrix().getTrans(); - const float zmin = pos.z() - mRange.z() - Constants::CellSizeInUnits; - const float zmax = pos.z() + mRange.z() + Constants::CellSizeInUnits; + const float zmin = pos.z() - mRange->z() - Constants::CellSizeInUnits; + const float zmax = pos.z() + mRange->z() + Constants::CellSizeInUnits; const float near = 0; const float far = zmax - zmin; - const float left = -mRange.x() / 2; + const float left = -mRange->x() / 2; const float right = -left; - const float top = mRange.y() / 2; + const float top = mRange->y() / 2; const float bottom = -top; if (SceneUtil::AutoDepth::isReversed()) @@ -163,10 +168,14 @@ namespace MWRender mSkyCullCallback = nullptr; mRootNode->removeChild(mCamera); + mRange = std::nullopt; } void PrecipitationOccluder::updateRange(const osg::Vec3f range) { + assert(range.x() != 0); + assert(range.y() != 0); + assert(range.z() != 0); const osg::Vec3f margin = { -50, -50, 0 }; mRange = range - margin; } diff --git a/apps/openmw/mwrender/precipitationocclusion.hpp b/apps/openmw/mwrender/precipitationocclusion.hpp index 26114ed42f..9d2992637e 100644 --- a/apps/openmw/mwrender/precipitationocclusion.hpp +++ b/apps/openmw/mwrender/precipitationocclusion.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace MWRender { class PrecipitationOccluder @@ -27,7 +29,7 @@ namespace MWRender osg::ref_ptr mCamera; osg::ref_ptr mSceneCamera; osg::ref_ptr mDepthTexture; - osg::Vec3f mRange; + std::optional mRange; }; } From 14e6af8bea50938bb610624166d1c90626af08b8 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 13 Jan 2024 15:42:17 +0400 Subject: [PATCH 14/47] Add a table with fadeOut argument for streamMusic --- apps/openmw/mwlua/soundbindings.cpp | 21 +++++++++++++++++++-- files/lua_api/openmw/ambient.lua | 12 +++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index dc45a672b4..c3c810e591 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -23,6 +23,11 @@ namespace float mTimeOffset = 0.f; }; + struct StreamMusicArgs + { + float mFade = 1.f; + }; + PlaySoundArgs getPlaySoundArgs(const sol::optional& options) { PlaySoundArgs args; @@ -55,6 +60,17 @@ namespace return MWSound::PlayMode::NoEnvNoScaling; return MWSound::PlayMode::NoEnv; } + + StreamMusicArgs getStreamMusicArgs(const sol::optional& options) + { + StreamMusicArgs args; + + if (options.has_value()) + { + args.mFade = options->get_or("fadeOut", 1.f); + } + return args; + } } namespace MWLua @@ -95,9 +111,10 @@ namespace MWLua return MWBase::Environment::get().getSoundManager()->getSoundPlaying(MWWorld::Ptr(), fileName); }; - api["streamMusic"] = [](std::string_view fileName) { + api["streamMusic"] = [](std::string_view fileName, const sol::optional& options) { + auto args = getStreamMusicArgs(options); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted); + sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted, args.mFade); }; api["isMusicPlaying"] = []() { return MWBase::Environment::get().getSoundManager()->isMusicPlaying(); }; diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index 917ec86c85..7153a6fb44 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -12,7 +12,7 @@ -- @param #string soundId ID of Sound record to play -- @param #table options An optional table with additional optional arguments. Can contain: -- --- * `timeOffset` - a floating point number >= 0, to some time (in second) from beginning of sound file (default: 0); +-- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from beginning of sound file (default: 0); -- * `volume` - a floating point number >= 0, to set a sound volume (default: 1); -- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); -- * `scale` - a boolean, to set if sound pitch should be scaled by simulation time scaling (default: true); @@ -32,7 +32,7 @@ -- @param #string fileName Path to sound file in VFS -- @param #table options An optional table with additional optional arguments. Can contain: -- --- * `timeOffset` - a floating point number >= 0, to some time (in second) from beginning of sound file (default: 0); +-- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from beginning of sound file (default: 0); -- * `volume` - a floating point number >= 0, to set a sound volume (default: 1); -- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); -- * `scale` - a boolean, to set if sound pitch should be scaled by simulation time scaling (default: true); @@ -76,7 +76,13 @@ -- Play a sound file as a music track -- @function [parent=#ambient] streamMusic -- @param #string fileName Path to file in VFS --- @usage ambient.streamMusic("Music\\Test\\Test.mp3"); +-- @param #table options An optional table with additional optional arguments. Can contain: +-- +-- * `fadeOut` - a floating point number >= 0, time (in seconds) to fade out current track before playing this one (default 1.0); +-- @usage local params = { +-- fadeOut=2.0 +-- }; +-- ambient.streamMusic("Music\\Test\\Test.mp3", params) --- -- Stop to play current music From f8c1d48c0befb1b5a9bc089ad2454406caa723ed Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 14 Jan 2024 10:54:51 +0400 Subject: [PATCH 15/47] Get rid of redundant casts --- apps/opencs/view/render/worldspacewidget.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index da02c1e179..f7732d752d 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -184,11 +184,11 @@ void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection() { - std::vector> selection = getSelection(~0u); + std::vector> selection = getSelection(Mask_Reference); for (std::vector>::iterator it = selection.begin(); it != selection.end(); ++it) { - if (CSVRender::ObjectTag* objectTag = dynamic_cast(it->get())) + if (CSVRender::ObjectTag* objectTag = static_cast(it->get())) { mOrbitCamControl->setCenter(objectTag->mObject->getPosition().asVec3()); } @@ -757,13 +757,14 @@ void CSVRender::WorldspaceWidget::toggleHiddenInstances() if (selection.empty()) return; - const CSVRender::ObjectTag* firstSelection = dynamic_cast(selection.begin()->get()); + const CSVRender::ObjectTag* firstSelection = static_cast(selection.begin()->get()); + assert(firstSelection != nullptr); const CSVRender::Mask firstMask = firstSelection->mObject->getRootNode()->getNodeMask() == Mask_Hidden ? Mask_Reference : Mask_Hidden; for (const auto& object : selection) - if (const auto objectTag = dynamic_cast(object.get())) + if (const auto objectTag = static_cast(object.get())) objectTag->mObject->getRootNode()->setNodeMask(firstMask); } From 98b281e4ad1145e07a6fb984ac9b290c6c95910c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 14 Jan 2024 10:59:39 +0400 Subject: [PATCH 16/47] Add a missing assertion --- apps/openmw/mwrender/pingpongcanvas.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 9c8b08adfd..3af937045f 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -288,6 +288,8 @@ namespace MWRender pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) .getTexture())); + assert(texture != nullptr); + texture->setTextureSize(w, h); texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels()); texture->dirtyTextureObject(); From 1a629cbf076a077d6d6ae9266fa9603a729ac2be Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 14 Jan 2024 10:09:46 +0300 Subject: [PATCH 17/47] Play shield hit sound for the shield that was hit (#7774) --- apps/openmw/mwclass/actor.cpp | 17 ----------------- apps/openmw/mwclass/actor.hpp | 2 -- apps/openmw/mwclass/creature.cpp | 3 --- apps/openmw/mwclass/npc.cpp | 3 --- apps/openmw/mwmechanics/combat.cpp | 9 +++++++++ apps/openmw/mwworld/class.cpp | 5 ----- apps/openmw/mwworld/class.hpp | 4 ---- 7 files changed, 9 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 9c197a70d2..0a45a85a74 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -37,23 +37,6 @@ namespace MWClass return true; } - void Actor::block(const MWWorld::Ptr& ptr) const - { - const MWWorld::InventoryStore& inv = getInventoryStore(ptr); - MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield == inv.end()) - return; - - MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); - const ESM::RefId skill = shield->getClass().getEquipmentSkill(*shield); - if (skill == ESM::Skill::LightArmor) - sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); - else if (skill == ESM::Skill::MediumArmor) - sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f); - else if (skill == ESM::Skill::HeavyArmor) - sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f); - } - osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const { MWMechanics::Movement& movement = getMovementSettings(ptr); diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 41d06cf5bd..cf0cb1eaa5 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -45,8 +45,6 @@ namespace MWClass bool useAnim() const override; - void block(const MWWorld::Ptr& ptr) const override; - osg::Vec3f getRotationVector(const MWWorld::Ptr& ptr) const override; ///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero. diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index bb9c1bc277..ab68cddda7 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -339,10 +339,7 @@ namespace MWClass MWMechanics::applyElementalShields(ptr, victim); if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) - { damage = 0; - victim.getClass().block(victim); - } MWMechanics::diseaseContact(victim, ptr); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4f295f7b35..f669547d1a 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -678,10 +678,7 @@ namespace MWClass MWMechanics::applyElementalShields(ptr, victim); if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) - { damage = 0; - victim.getClass().block(victim); - } if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState()) damage = 0; diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 3f17df96fd..3208ea2293 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -135,6 +135,15 @@ namespace MWMechanics auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (Misc::Rng::roll0to99(prng) < x) { + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + const ESM::RefId skill = shield->getClass().getEquipmentSkill(*shield); + if (skill == ESM::Skill::LightArmor) + sndMgr->playSound3D(blocker, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); + else if (skill == ESM::Skill::MediumArmor) + sndMgr->playSound3D(blocker, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f); + else if (skill == ESM::Skill::HeavyArmor) + sndMgr->playSound3D(blocker, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f); + // Reduce shield durability by incoming damage int shieldhealth = shield->getClass().getItemHealth(*shield); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index d5062d6add..88d9d744e2 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -118,11 +118,6 @@ namespace MWWorld throw std::runtime_error("class cannot hit"); } - void Class::block(const Ptr& ptr) const - { - throw std::runtime_error("class cannot block"); - } - void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const { diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 87e70b3198..55cc62c78d 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -151,10 +151,6 @@ namespace MWWorld /// actor responsible for the attack. \a successful specifies if the hit is /// successful or not. \a sourceType classifies the damage source. - virtual void block(const Ptr& ptr) const; - ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield - /// (default implementation: throw an exception) - virtual std::unique_ptr activate(const Ptr& ptr, const Ptr& actor) const; ///< Generate action for activation (default implementation: return a null action). From 6cefe2c118c8de160d61008a40d02bfc09b43901 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 13 Jan 2024 21:34:44 +0400 Subject: [PATCH 18/47] Rework launcher tabs --- apps/launcher/graphicspage.cpp | 106 -- apps/launcher/graphicspage.hpp | 1 - apps/launcher/settingspage.cpp | 101 ++ apps/launcher/settingspage.hpp | 1 + apps/launcher/ui/graphicspage.ui | 632 +++---- apps/launcher/ui/importpage.ui | 22 +- apps/launcher/ui/mainwindow.ui | 18 +- apps/launcher/ui/settingspage.ui | 1482 ++++++++++------- .../source/reference/modding/settings/GUI.rst | 6 +- .../source/reference/modding/settings/fog.rst | 8 + .../reference/modding/settings/game.rst | 44 +- .../reference/modding/settings/general.rst | 4 +- .../reference/modding/settings/shadows.rst | 20 + .../reference/modding/settings/sound.rst | 9 +- .../reference/modding/settings/video.rst | 14 +- 15 files changed, 1263 insertions(+), 1205 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index b360c215e6..735bcf1df1 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -34,7 +34,6 @@ Launcher::GraphicsPage::GraphicsPage(QWidget* parent) connect(standardRadioButton, &QRadioButton::toggled, this, &GraphicsPage::slotStandardToggled); connect(screenComboBox, qOverload(&QComboBox::currentIndexChanged), this, &GraphicsPage::screenChanged); connect(framerateLimitCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotFramerateLimitToggled); - connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotShadowDistLimitToggled); } bool Launcher::GraphicsPage::setupSDL() @@ -126,58 +125,6 @@ bool Launcher::GraphicsPage::loadSettings() framerateLimitSpinBox->setValue(fpsLimit); } - // Lighting - int lightingMethod = 1; - switch (Settings::shaders().mLightingMethod) - { - case SceneUtil::LightingMethod::FFP: - lightingMethod = 0; - break; - case SceneUtil::LightingMethod::PerObjectUniform: - lightingMethod = 1; - break; - case SceneUtil::LightingMethod::SingleUBO: - lightingMethod = 2; - break; - } - lightingMethodComboBox->setCurrentIndex(lightingMethod); - - // Shadows - if (Settings::shadows().mActorShadows) - actorShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::shadows().mPlayerShadows) - playerShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::shadows().mTerrainShadows) - terrainShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::shadows().mObjectShadows) - objectShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::shadows().mEnableIndoorShadows) - indoorShadowsCheckBox->setCheckState(Qt::Checked); - - const auto& boundMethod = Settings::shadows().mComputeSceneBounds.get(); - if (boundMethod == "bounds") - shadowComputeSceneBoundsComboBox->setCurrentIndex(0); - else if (boundMethod == "primitives") - shadowComputeSceneBoundsComboBox->setCurrentIndex(1); - else - shadowComputeSceneBoundsComboBox->setCurrentIndex(2); - - const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance; - if (shadowDistLimit > 0) - { - shadowDistanceCheckBox->setCheckState(Qt::Checked); - shadowDistanceSpinBox->setValue(shadowDistLimit); - } - - const float shadowFadeStart = Settings::shadows().mShadowFadeStart; - if (shadowFadeStart != 0) - fadeStartSpinBox->setValue(shadowFadeStart); - - const int shadowRes = Settings::shadows().mShadowMapResolution; - int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); - if (shadowResIndex != -1) - shadowResolutionComboBox->setCurrentIndex(shadowResIndex); - return true; } @@ -220,53 +167,6 @@ void Launcher::GraphicsPage::saveSettings() { Settings::video().mFramerateLimit.set(0); } - - // Lighting - static constexpr std::array lightingMethodMap = { - SceneUtil::LightingMethod::FFP, - SceneUtil::LightingMethod::PerObjectUniform, - SceneUtil::LightingMethod::SingleUBO, - }; - Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]); - - // Shadows - const int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; - Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist); - const float cFadeStart = fadeStartSpinBox->value(); - if (cShadowDist > 0) - Settings::shadows().mShadowFadeStart.set(cFadeStart); - - const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked; - const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked; - const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked; - const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked; - if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows) - { - Settings::shadows().mEnableShadows.set(true); - Settings::shadows().mActorShadows.set(cActorShadows); - Settings::shadows().mPlayerShadows.set(cPlayerShadows); - Settings::shadows().mObjectShadows.set(cObjectShadows); - Settings::shadows().mTerrainShadows.set(cTerrainShadows); - } - else - { - Settings::shadows().mEnableShadows.set(false); - Settings::shadows().mActorShadows.set(false); - Settings::shadows().mPlayerShadows.set(false); - Settings::shadows().mObjectShadows.set(false); - Settings::shadows().mTerrainShadows.set(false); - } - - Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked); - Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt()); - - auto index = shadowComputeSceneBoundsComboBox->currentIndex(); - if (index == 0) - Settings::shadows().mComputeSceneBounds.set("bounds"); - else if (index == 1) - Settings::shadows().mComputeSceneBounds.set("primitives"); - else - Settings::shadows().mComputeSceneBounds.set("none"); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) @@ -377,9 +277,3 @@ void Launcher::GraphicsPage::slotFramerateLimitToggled(bool checked) { framerateLimitSpinBox->setEnabled(checked); } - -void Launcher::GraphicsPage::slotShadowDistLimitToggled(bool checked) -{ - shadowDistanceSpinBox->setEnabled(checked); - fadeStartSpinBox->setEnabled(checked); -} diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 85f91d1ff1..928ec9f1a2 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -31,7 +31,6 @@ namespace Launcher void slotFullScreenChanged(int state); void slotStandardToggled(bool checked); void slotFramerateLimitToggled(bool checked); - void slotShadowDistLimitToggled(bool checked); private: QVector mResolutionsPerScreen; diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index b8539671b5..9492326cb0 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -212,6 +212,55 @@ bool Launcher::SettingsPage::loadSettings() loadSettingBool(Settings::fog().mExponentialFog, *exponentialFogCheckBox); loadSettingBool(Settings::fog().mSkyBlending, *skyBlendingCheckBox); skyBlendingStartComboBox->setValue(Settings::fog().mSkyBlendingStart); + + int lightingMethod = 1; + switch (Settings::shaders().mLightingMethod) + { + case SceneUtil::LightingMethod::FFP: + lightingMethod = 0; + break; + case SceneUtil::LightingMethod::PerObjectUniform: + lightingMethod = 1; + break; + case SceneUtil::LightingMethod::SingleUBO: + lightingMethod = 2; + break; + } + lightingMethodComboBox->setCurrentIndex(lightingMethod); + + loadSettingBool(Settings::shadows().mActorShadows, *actorShadowsCheckBox); + loadSettingBool(Settings::shadows().mPlayerShadows, *playerShadowsCheckBox); + loadSettingBool(Settings::shadows().mTerrainShadows, *terrainShadowsCheckBox); + loadSettingBool(Settings::shadows().mObjectShadows, *objectShadowsCheckBox); + loadSettingBool(Settings::shadows().mEnableIndoorShadows, *indoorShadowsCheckBox); + + const auto& boundMethod = Settings::shadows().mComputeSceneBounds.get(); + if (boundMethod == "bounds") + shadowComputeSceneBoundsComboBox->setCurrentIndex(0); + else if (boundMethod == "primitives") + shadowComputeSceneBoundsComboBox->setCurrentIndex(1); + else + shadowComputeSceneBoundsComboBox->setCurrentIndex(2); + + const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance; + if (shadowDistLimit > 0) + { + shadowDistanceCheckBox->setCheckState(Qt::Checked); + shadowDistanceSpinBox->setValue(shadowDistLimit); + shadowDistanceSpinBox->setEnabled(true); + fadeStartSpinBox->setEnabled(true); + } + + const float shadowFadeStart = Settings::shadows().mShadowFadeStart; + if (shadowFadeStart != 0) + fadeStartSpinBox->setValue(shadowFadeStart); + + const int shadowRes = Settings::shadows().mShadowMapResolution; + int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); + if (shadowResIndex != -1) + shadowResolutionComboBox->setCurrentIndex(shadowResIndex); + + connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotShadowDistLimitToggled); } // Audio @@ -359,6 +408,52 @@ void Launcher::SettingsPage::saveSettings() saveSettingBool(*exponentialFogCheckBox, Settings::fog().mExponentialFog); saveSettingBool(*skyBlendingCheckBox, Settings::fog().mSkyBlending); Settings::fog().mSkyBlendingStart.set(skyBlendingStartComboBox->value()); + + static constexpr std::array lightingMethodMap = { + SceneUtil::LightingMethod::FFP, + SceneUtil::LightingMethod::PerObjectUniform, + SceneUtil::LightingMethod::SingleUBO, + }; + Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]); + + const int cShadowDist + = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; + Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist); + const float cFadeStart = fadeStartSpinBox->value(); + if (cShadowDist > 0) + Settings::shadows().mShadowFadeStart.set(cFadeStart); + + const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked; + if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows) + { + Settings::shadows().mEnableShadows.set(true); + Settings::shadows().mActorShadows.set(cActorShadows); + Settings::shadows().mPlayerShadows.set(cPlayerShadows); + Settings::shadows().mObjectShadows.set(cObjectShadows); + Settings::shadows().mTerrainShadows.set(cTerrainShadows); + } + else + { + Settings::shadows().mEnableShadows.set(false); + Settings::shadows().mActorShadows.set(false); + Settings::shadows().mPlayerShadows.set(false); + Settings::shadows().mObjectShadows.set(false); + Settings::shadows().mTerrainShadows.set(false); + } + + Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked); + Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt()); + + auto index = shadowComputeSceneBoundsComboBox->currentIndex(); + if (index == 0) + Settings::shadows().mComputeSceneBounds.set("bounds"); + else if (index == 1) + Settings::shadows().mComputeSceneBounds.set("primitives"); + else + Settings::shadows().mComputeSceneBounds.set("none"); } // Audio @@ -461,3 +556,9 @@ void Launcher::SettingsPage::slotSkyBlendingToggled(bool checked) skyBlendingStartComboBox->setEnabled(checked); skyBlendingStartLabel->setEnabled(checked); } + +void Launcher::SettingsPage::slotShadowDistLimitToggled(bool checked) +{ + shadowDistanceSpinBox->setEnabled(checked); + fadeStartSpinBox->setEnabled(checked); +} diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index 9f7d6b1f43..a8a6b7c26d 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -32,6 +32,7 @@ namespace Launcher void slotAnimSourcesToggled(bool checked); void slotPostProcessToggled(bool checked); void slotSkyBlendingToggled(bool checked); + void slotShadowDistLimitToggled(bool checked); private: Config::GameSettings& mGameSettings; diff --git a/apps/launcher/ui/graphicspage.ui b/apps/launcher/ui/graphicspage.ui index c0e2b0be06..fa92c7b789 100644 --- a/apps/launcher/ui/graphicspage.ui +++ b/apps/launcher/ui/graphicspage.ui @@ -11,459 +11,229 @@ - - - - 0 - - - - Display - - - - - - + + + + + + + + + Screen + + + + + + + Window mode + + + + + + + + + + + 800 + + + + + + + × + + + + + + + 600 + + + + + + + + + Custom: + + + + + + + Standard: + + + true + + + + + + + + + + + - Screen: + 0 - - - - - - - 0 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - - - - - + + - Window Mode: + 2 - - - - + + - Resolution: + 4 - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - + + - Vertical Sync: + 8 - - - - + + - Anti-aliasing: + 16 - - - - - - - - - - 800 - - - - - - - × - - - - - - - 600 - - - - - - - - - Custom: - - - - - - - Standard: - - - true - - - - - - - - - - - - 0 - - - - Fullscreen - - - - - Windowed Fullscreen - - - - - Windowed - - - - - - + + + + + + + Framerate limit + + + + + + + Window border + + + + + + + + + + 0 + + - Window Border + Disabled - - - - - - 0 - - - - Disabled - - - - - Enabled - - - - - Adaptive - - - - - - + + - Framerate Limit: + Enabled - - - - - - false - - - FPS - - - 1 - - - 1.000000000000000 - - - 1000.000000000000000 - - - 15.000000000000000 - - - 300.000000000000000 - - - - - - - - - - Lighting - - - - - - + + - Lighting Method: - - - - - - - - legacy - - - - - shaders compatibility - - - - - shaders - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Shadows - - - - - - - - <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + Adaptive + + + + + + + 0 + + - Enable Player Shadows - - - - - - - <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + Fullscreen + + - Enable Actor Shadows - - - - - - - <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + Windowed Fullscreen + + - Enable Object Shadows + Windowed - - - - - - <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - - - Enable Terrain Shadows - - - - - - - <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - - - Enable Indoor Shadows - - - - - - - <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - - - Shadow Near Far Computation Method: - - - - - - - - bounds - - - - - primitives - - - - - none - - - - - - - - <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - - - Shadow Map Resolution: - - - - - - - - 512 - - - - - 1024 - - - - - 2048 - - - - - 4096 - - - - - - - - <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - - - Shadow Distance Limit: - - - - - - - false - - - <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> - - - unit(s) - - - 512 - - - 81920 - - - 8192 - - - - - - - <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - - - Fade Start Multiplier: - - - - - - - false - - - 2 - - - 0.000000000000000 - - - 1.000000000000000 - - - 0.900000000000000 - - - - - - - + + + + + + + Resolution + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + false + + + FPS + + + 1 + + + 1.000000000000000 + + + 1000.000000000000000 + + + 15.000000000000000 + + + 300.000000000000000 + + + + + + + Anti-aliasing + + + + + + + Vertical synchronization + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + diff --git a/apps/launcher/ui/importpage.ui b/apps/launcher/ui/importpage.ui index 3e2b0c5e64..4626d29e8a 100644 --- a/apps/launcher/ui/importpage.ui +++ b/apps/launcher/ui/importpage.ui @@ -6,7 +6,7 @@ 0 0 - 514 + 515 397 @@ -129,16 +129,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov + + + + Qt::Vertical + + + + 0 + 0 + + + + - - - - Qt::Vertical - - - diff --git a/apps/launcher/ui/mainwindow.ui b/apps/launcher/ui/mainwindow.ui index 54a369999d..b05a9d2c3b 100644 --- a/apps/launcher/ui/mainwindow.ui +++ b/apps/launcher/ui/mainwindow.ui @@ -7,20 +7,20 @@ 0 0 720 - 565 + 635 720 - 565 + 635 OpenMW Launcher - + :/images/openmw.png:/images/openmw.png @@ -120,7 +120,7 @@ QToolButton { true - + :/images/openmw-plugin.png:/images/openmw-plugin.png @@ -141,11 +141,11 @@ QToolButton { true - + :/images/preferences-video.png:/images/preferences-video.png - Graphics + Display Allows to change graphics settings @@ -156,7 +156,7 @@ QToolButton { true - + :/images/preferences.png:/images/preferences.png @@ -171,7 +171,7 @@ QToolButton { true - + :/images/preferences-advanced.png:/images/preferences-advanced.png @@ -183,7 +183,7 @@ QToolButton { - + diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 0340509205..cf8215ef30 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -6,7 +6,7 @@ 0 0 - 732 + 741 503 @@ -14,36 +14,16 @@ - 1 + 0 - + - Game Mechanics + Gameplay - - - - <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - - - Permanent barter disposition changes - - - - - - - <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - - - Followers defend immediately - - - - + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> @@ -53,7 +33,37 @@ - + + + + Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. + + + Always allow actors to follow over water + + + + + + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + + + Permanent barter disposition changes + + + + + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + + + Racial variation in speed fix + + + + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> @@ -63,7 +73,17 @@ - + + + + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + + + NPCs avoid collisions + + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> @@ -73,47 +93,27 @@ - - + + - <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - Swim upward correction + Day night switch nodes - - + + - <html><head/><body><p>Make enchanted weaponry without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - Enchanted weapons are magical + Followers defend immediately - - - - <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - - - Always allow stealing from knocked out actors - - - - - - - <html><head/><body><p>Effects of reflected Absorb spells are not mirrored -- like in Morrowind.</p></body></html> - - - Classic reflected Absorb spells behavior - - - - + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> @@ -124,26 +124,66 @@ - + - <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - NPCs avoid collisions + Only magical ammo bypass resistance - - + + - <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - Racial variation in speed fix + Graphic herbalism - + + + + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + + + Swim upward correction + + + + + + + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + + + Enchanted weapons are magical + + + + + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + + + Merchant equipping fix + + + + + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + + + Trainers choose offered skills by base value + + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> @@ -153,44 +193,44 @@ - - + + - Give NPC an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - Always allow NPC to follow over water surface + Steal from knocked out actors in combat + + + + + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored -- like in Morrowind.</p></body></html> + + + Classic reflected Absorb spells behavior + + + + + + + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + + + Unarmed creature attacks damage armor - - - - <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - - - Unarmed creature attacks damage armor - - - - - - - <html><head/><body><p>Allow non-standard ammunition solely to bypass normal weapon resistance or weakness.</p></body></html> - - - Only appropriate ammunition bypasses normal weapon resistance - - - - Factor strength into hand-to-hand combat: + Factor strength into hand-to-hand combat @@ -222,7 +262,7 @@ <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - Background physics threads: + Background physics threads @@ -232,14 +272,14 @@ - Actor collision shape type: + Actor collision shape type - Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency bewtween available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. Axis-aligned bounding box @@ -284,492 +324,746 @@ - - - true - - - - - 0 - 0 - 671 - 774 - - - - - - - Animations - - - - - - <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - - - Use magic item animation - - - - - - - <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - - - Smooth movement - - - - - - - <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - - - Use additional animation sources - - - - - - - <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - - - Turn to movement direction - - - - - - - 20 - - - - - false - - - <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - - - Weapon sheathing - - - - - - - false - - - <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - - - Shield sheathing - - - - - - - - - <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - - - Player movement ignores animation - - - - - - - - - - Shaders - - - - - - <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + + + + + 0 + + + + Animations + + + + + + + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + + + Smooth movement + + + + + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + + + Use additional animation sources + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + + + Turn to movement direction + + + + + + + false + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + + + Weapon sheathing + + + + + + + false + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + + + Shield sheathing + + + + + + + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + + + Player movement ignores animation + + + + + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + + + Use magic item animation + + + + + + + + + + Shaders + + + + + + + + + + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - - - Auto use object normal maps - - - - - - - <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - - - Auto use terrain normal maps - - - - - - - <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + + + Auto use object normal maps + + + + + + + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + + + Soft particles + + + + + + + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately (see 'specular map pattern', e.g. for a base texture foo.dds, the specular map texture would have to be named foo_spec.dds). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.osg file, not supported in .nif files). Affects objects.</p></body></html> - - - Auto use object specular maps - - - - - - - <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - - - Auto use terrain specular maps - - - - - - - <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + + + Auto use object specular maps + + + + + + + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + + + Auto use terrain normal maps + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + + + Auto use terrain specular maps + + + + + + + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + + + Adjust coverage for alpha test + + + + + + + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + + + Use anti-alias alpha testing + + + + + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. Affected objects will use shaders. </p></body></html> - - - Bump/reflect map local lighting - - - - - - - <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - - - Use anti-alias alpha testing - - - - - - - <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - - - Soft Particles - - - - - - - <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - - - Adjust coverage for alpha test - - - - - - - <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - - - Weather Particle Occlusion - - - - - - - - - - Fog - - - - - - <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + + + Bump/reflect map local lighting + + + + + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + + + Weather particle occlusion + + + + + + + + + + + + Fog + + + + + + + + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + + + Exponential fog + + + + + + + false + + + 3 + + + 0.000000000000000 + + + 1.000000000000000 + + + 0.005000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - - - Radial fog - - - - - - - <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - - - Exponential fog - - - - - - - <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - - - Sky blending - - - - - - - false - - - <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - - - Sky blending start - - - - - - - false - - - 3 - - - 0.000000000000000 - - - 1.000000000000000 - - - 0.005000000000000 - - - - - - - - - - Terrain - - - - - - + + + Radial fog + + + + + + + false + + + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + + + Sky blending start + + + + + + + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + + + Sky blending + + + + + + + + + + Terrain + + + + + + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + + + Distant land + + + + + + + cells + + + 0.000000000000000 + + + 0.500000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + + + Object paging min size + + + + + + + Viewing distance + + + + + + + 3 + + + 0.000000000000000 + + + 0.250000000000000 + + + 0.005000000000000 + + + + + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + + + Active grid object paging + + + + + + + + + + Post Processing + + + + + + + + false + + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + + + Auto exposure speed + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + false + + + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + + + Transparent postpass + + + + + + + false + + + 3 + + + 0.010000000000000 + + + 10.000000000000000 + + + 0.001000000000000 + + + + + + + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + + + Enable post processing + + + + + + + + + + Shadows + + + + + + + - Viewing distance - - - - - - - Cells - - - 0.000000000000000 - - - 0.500000000000000 - - - - - - - - - - - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + bounds + + - Object paging min size - - - - - - - 3 - - - 0.000000000000000 - - - 0.250000000000000 - - - 0.005000000000000 - - - - - - - - - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - - - Distant land - - - - - - - 20 - - - - - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + primitives + + - Active grid object paging - - - - - - - - - - - - Models - - - - - - <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - - - Day night switch nodes - - - - - - - - - - Post Processing - - - - - - <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - - - Enable post processing - - - - - - - 20 - - - - - false - - - <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + none + + + + + + + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + + + Shadow planes computation method + + + + + + + false + + + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + + + unit(s) + + + 512 + + + 81920 + + + 8192 + + + + + + + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + + + Enable actor shadows + + + + + + - Transparent postpass + 512 - - - - - - - - false - - - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - - - Auto exposure speed - - - - - - - false - - - 3 - - - 0.010000000000000 - - - 10.000000000000000 - - - 0.001000000000000 - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - + + + + 1024 + + + + + 2048 + + + + + 4096 + + + + + + + + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + + + Fade start multiplier + + + + + + + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + + + Enable player shadows + + + + + + + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + + + Shadow map resolution + + + + + + + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + + + Shadow distance limit: + + + + + + + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + + + Enable object shadows + + + + + + + false + + + 2 + + + 0.000000000000000 + + + 1.000000000000000 + + + 0.900000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + + + Enable indoor shadows + + + + + + + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + + + Enable terrain shadows + + + + + + + + + + Lighting + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + legacy + + + + + shaders compatibility + + + + + shaders + + + + + + + + Lighting method + + + + + + + + + + @@ -786,7 +1080,7 @@ Select your preferred audio device. - Audio Device + Audio device @@ -872,7 +1166,7 @@ Select your preferred HRTF profile. - HRTF Profile + HRTF profile @@ -936,17 +1230,17 @@ - Tool Tip Only + Tooltip - Crosshair Only + Crosshair - Tool Tip and Crosshair + Tooltip and crosshair @@ -1036,16 +1330,6 @@ - - - - <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - - - Enable graphic herbalism - - - @@ -1110,46 +1394,6 @@ - - - Bug Fixes - - - - - - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - - - Merchant equipping fix - - - - - - - <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - - - Trainers choose their training skills based on their base skill points - - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - Miscellaneous @@ -1176,7 +1420,7 @@ - Maximum Quicksaves + Maximum quicksaves @@ -1193,9 +1437,9 @@ - + - Other + Screenshots @@ -1203,7 +1447,7 @@ - Screenshot Format + Screenshot format diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index 76c83be4da..edacdc730a 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -11,7 +11,7 @@ scaling factor This setting scales GUI windows. A value of 1.0 results in the normal scale. Larger values are useful to increase the scale of the GUI for high resolution displays. -This setting can be configured in the Interface section of the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. font size --------- @@ -24,7 +24,7 @@ Allows to specify glyph size for in-game fonts. Note: default bitmap fonts are supposed to work with 16px size, otherwise glyphs will be blurry. TrueType fonts do not have this issue. -This setting can be configured in the Interface section of the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. menu transparency ----------------- @@ -65,7 +65,7 @@ The Bethesda provided assets have a 4:3 aspect ratio, but other assets are permi If this setting is false, the assets will be centered in the mentioned 4:3 aspect ratio, with black bars filling the remainder of the screen. -This setting can be configured in the Interface section of the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. subtitles --------- diff --git a/docs/source/reference/modding/settings/fog.rst b/docs/source/reference/modding/settings/fog.rst index b05112162e..20bfdaf14d 100644 --- a/docs/source/reference/modding/settings/fog.rst +++ b/docs/source/reference/modding/settings/fog.rst @@ -125,6 +125,8 @@ By default, the fog becomes thicker proportionally to your distance from the cli This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. +This setting can be controlled in the Settings tab of the launcher. + exponential fog --------------- @@ -135,6 +137,8 @@ exponential fog Similar to "radial fog" but uses an exponential formula for the fog. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. +This setting can be controlled in the Settings tab of the launcher. + sky blending ------------ @@ -146,6 +150,8 @@ Whether to use blending with the sky for everything that is close to the clippin If enabled the clipping plane becomes invisible. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. +This setting can be controlled in the Settings tab of the launcher. + sky blending start ------------------ @@ -155,6 +161,8 @@ sky blending start The fraction of the maximum distance at which blending with the sky starts. +This setting can be controlled in the Settings tab of the launcher. + sky rtt resolution ------------------ diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 31cc2703f2..368401f5c5 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -16,7 +16,7 @@ If the setting is 2, the crosshair is the colour of the colour crosshair owned s If the setting is 3, both the tool tip background and the crosshair are coloured. The crosshair is not visible if crosshair is false. -This setting can be configured in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. show projectile damage ---------------------- @@ -27,7 +27,7 @@ show projectile damage If this setting is true, the damage bonus of arrows and bolts will show on their tooltip. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. show melee info --------------- @@ -38,7 +38,7 @@ show melee info If this setting is true, the reach and speed of weapons will show on their tooltip. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. show enchant chance ------------------- @@ -49,7 +49,7 @@ show enchant chance Whether or not the chance of success will be displayed in the enchanting menu. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. best attack ----------- @@ -78,7 +78,7 @@ If this setting is false, player has to wait until end of death animation in all Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. difficulty ---------- @@ -123,7 +123,7 @@ and the caster will absorb their own stat resulting in no effect on either the c This makes the gameplay as a mage easier, but these spells become imbalanced. This is how Morrowind behaves. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. classic calm spells behavior ---------------------------------------- @@ -137,7 +137,7 @@ This means that a Calm spell of any magnitude will always take actors out of com This is how Morrowind behaves without the Morrowind Code Patch. If this setting is off, Calm spells will only take their target out of combat once. Allowing them to re-engage if the spell was not sufficiently strong. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. use magic item animations ------------------------- @@ -161,7 +161,7 @@ show effect duration Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. enchanted weapons are magical ----------------------------- @@ -173,7 +173,7 @@ enchanted weapons are magical Make enchanted weapons without Magical flag bypass normal weapons resistance (and weakness) certain creatures have. This is how Morrowind behaves. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. prevent merchant equipping -------------------------- @@ -184,7 +184,7 @@ prevent merchant equipping Prevent merchants from equipping items that are sold to them. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. followers attack on sight ------------------------- @@ -196,7 +196,8 @@ followers attack on sight Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first. Please note this setting has not been extensively tested and could have side effects with certain quests. -This setting can be toggled in the Settings tab of the launcher. + +This setting can be controlled in the Settings tab of the launcher. shield sheathing ---------------- @@ -214,6 +215,8 @@ To avoid conflicts, you can use _sh mesh without "Bip01 Sheath" node for such "s Also you can use an _sh node with empty "Bip01 Sheath" node. In this case the engine will use basic shield model, but will use transformations from the "Bip01 Sheath" node. +This setting can be controlled in the Settings tab of the launcher. + weapon sheathing ---------------- @@ -226,6 +229,8 @@ If this setting is true, OpenMW will utilize weapon sheathing-compatible assets To make use of this, you need to have an xbase_anim_sh.nif file with weapon bones that will be injected into the skeleton. Additional _sh suffix models are not essential for weapon sheathing to work but will act as quivers or scabbards for the weapons they correspond to. +This setting can be controlled in the Settings tab of the launcher. + use additional anim sources --------------------------- @@ -238,7 +243,8 @@ For example, if the main animation mesh has name Meshes/x.nif, the engine will load all KF-files from Animations/x folder and its child folders. This can be useful if you want to use several animation replacers without merging them. Attention: animations from AnimKit have their own format and are not supposed to be directly loaded in-game! -This setting can only be configured by editing the settings configuration file. + +This setting can be controlled in the Settings tab of the launcher. barter disposition change is permanent -------------------------------------- @@ -251,7 +257,7 @@ If this setting is true, disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. This imitates the option that Morrowind Code Patch offers. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. only appropriate ammunition bypasses resistance ----------------------------------------------- @@ -264,7 +270,7 @@ If this setting is true, you will have to use the appropriate ammunition to bypa An enchanted bow with chitin arrows will no longer be enough for the purpose, while a steel longbow with glass arrows will still work. This was previously the default engine behavior that diverged from Morrowind design. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. strength influences hand to hand -------------------------------- @@ -454,6 +460,8 @@ Some mods add harvestable container models. When this setting is enabled, activa When this setting is turned off or when activating a regular container, the menu will open as usual. +This setting can be controlled in the Settings tab of the launcher. + allow actors to follow over water surface ----------------------------------------- @@ -489,6 +497,8 @@ day night switches Some mods add models which change visuals based on time of day. When this setting is enabled, supporting models will automatically make use of Day/night state. +This setting can be controlled in the Settings tab of the launcher. + unarmed creature attacks damage armor ------------------------------------- @@ -500,7 +510,7 @@ If disabled unarmed creature attacks do not reduce armor condition, just as with If enabled unarmed creature attacks reduce armor condition, the same as attacks from NPCs and armed creatures. -This setting can be controlled in the Settings tab of the launcher, under Game Mechanics. +This setting can be controlled in the Settings tab of the launcher. actor collision shape type -------------------------- @@ -518,6 +528,8 @@ will not be useful with another. * 1: Rotating box * 2: Cylinder +This setting can be controlled in the Settings tab of the launcher. + player movement ignores animation --------------------------------- @@ -528,4 +540,4 @@ player movement ignores animation In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. -This setting can be controlled in the Settings tab of the launcher, under Visuals. +This setting can be controlled in the Settings tab of the launcher. diff --git a/docs/source/reference/modding/settings/general.rst b/docs/source/reference/modding/settings/general.rst index e56b84ab89..d7afc04349 100644 --- a/docs/source/reference/modding/settings/general.rst +++ b/docs/source/reference/modding/settings/general.rst @@ -29,7 +29,7 @@ Specify the format for screen shots taken by pressing the screen shot key (bound This setting should be the file extension commonly associated with the desired format. The formats supported will be determined at compilation, but "jpg", "png", and "tga" should be allowed. -This setting can be configured in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. texture mag filter ------------------ @@ -69,6 +69,8 @@ notify on saved screenshot Show message box when screenshot is saved to a file. +This setting can be controlled in the Settings tab of the launcher. + preferred locales ----------------- diff --git a/docs/source/reference/modding/settings/shadows.rst b/docs/source/reference/modding/settings/shadows.rst index 0670a81092..c7f7958edd 100644 --- a/docs/source/reference/modding/settings/shadows.rst +++ b/docs/source/reference/modding/settings/shadows.rst @@ -16,6 +16,8 @@ Unlike in the original Morrowind engine, 'Shadow Mapping' is used, which can hav Bear in mind that this will force OpenMW to use shaders as if :ref:`force shaders` was enabled. A keen developer may be able to implement compatibility with fixed-function mode using the advice of `this post `_, but it may be more difficult than it seems. +This setting can be controlled in the Settings tab of the launcher. + number of shadow maps --------------------- @@ -38,6 +40,8 @@ The maximum distance from the camera shadows cover, limiting their overall area and improving their quality and performance at the cost of removing shadows of distant objects or terrain. Set this to a non-positive value to remove the limit. +This setting can be controlled in the Settings tab of the launcher. + shadow fade start ------------------- @@ -49,6 +53,8 @@ The fraction of the maximum shadow map distance at which the shadows will begin Tweaking it will make the transition proportionally more or less smooth. This setting has no effect if the maximum shadow map distance is non-positive (infinite). +This setting can be controlled in the Settings tab of the launcher. + allow shadow map overlap ------------------------ @@ -90,6 +96,8 @@ compute scene bounds Two different ways to make better use of shadow map(s) by making them cover a smaller area. While primitives give better shadows at expense of more CPU, bounds gives better performance overall but with lower quality shadows. There is also the ability to disable this computation with none. +This setting can be controlled in the Settings tab of the launcher. + shadow map resolution --------------------- @@ -101,6 +109,8 @@ Control 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 than smaller values which are not powers of two on some GPU/driver combinations. +This setting can be controlled in the Settings tab of the launcher. + actor shadows ------------- @@ -111,6 +121,8 @@ actor shadows Allow actors to cast shadows. Potentially decreases performance. +This setting can be controlled in the Settings tab of the launcher. + player shadows -------------- @@ -121,6 +133,8 @@ player shadows Allow the player to cast shadows. Potentially decreases performance. +This setting can be controlled in the Settings tab of the launcher. + terrain shadows --------------- @@ -131,6 +145,8 @@ terrain shadows Allow terrain to cast shadows. Potentially decreases performance. +This setting can be controlled in the Settings tab of the launcher. + object shadows -------------- @@ -141,6 +157,8 @@ object shadows Allow static objects to cast shadows. Potentially decreases performance. +This setting can be controlled in the Settings tab of the launcher. + enable indoor shadows --------------------- @@ -152,6 +170,8 @@ Allow shadows indoors. Due to limitations with Morrowind's data, only actors can cast shadows indoors without the ceiling casting a shadow everywhere. Some might feel this is distracting as shadows can be cast through other objects, so indoor shadows can be disabled completely. +This setting can be controlled in the Settings tab of the launcher. + Expert settings *************** diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index 4cc665582b..7a5718735c 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -13,7 +13,7 @@ which should usually be sufficient, but if you need to explicitly specify a devi The names of detected devices can be found in the openmw.log file in your configuration directory. -This setting can be configured by editing the settings configuration file, or in the Audio tab of the OpenMW Launcher. +This setting can be controlled in the Settings tab of the launcher. master volume ------------- @@ -111,7 +111,8 @@ Enabling HRTF may also require an OpenAL Soft version greater than 1.17.0, and possibly some operating system configuration. A value of 0 disables HRTF processing, while a value of 1 explicitly enables HRTF processing. The default value is -1, which should enable the feature automatically for most users when possible. -This setting can be configured by editing the settings configuration file, or in the Audio tab of the OpenMW Launcher. + +This setting can be controlled in the Settings tab of the launcher. hrtf ---- @@ -123,6 +124,6 @@ hrtf This setting specifies which HRTF profile to use when HRTF is enabled. Blank means use the default. This setting has no effect if HRTF is not enabled based on the hrtf enable setting. Allowed values for this field are enumerated in openmw.log file is an HRTF enabled audio system is installed. - The default value is empty, which uses the default profile. -This setting can be configured by editing the settings configuration file, or in the Audio tab of the OpenMW Launcher. + +This setting can be controlled in the Settings tab of the launcher. diff --git a/docs/source/reference/modding/settings/video.rst b/docs/source/reference/modding/settings/video.rst index 801cf63d5b..46016247ff 100644 --- a/docs/source/reference/modding/settings/video.rst +++ b/docs/source/reference/modding/settings/video.rst @@ -13,8 +13,8 @@ Larger values produce more detailed images within the constraints of your graphi but may reduce the frame rate. The window resolution can be selected from a menu of common screen sizes -in the Video tab of the Video Panel of the Options menu, or in the Graphics tab of the OpenMW Launcher. -The horizontal resolution can also be set to a custom value in the Graphics tab of the OpenMW Launcher. +in the Video tab of the Video Panel of the Options menu, or in the Display tab of the launcher. +The horizontal resolution can also be set to a custom value in the Display tab of the launcher. resolution y ------------ @@ -28,8 +28,8 @@ Larger values produce more detailed images within the constraints of your graphi but may reduce the frame rate. The window resolution can be selected from a menu of common screen sizes -in the Video tab of the Video Panel of the Options menu, or in the Graphics tab of the OpenMW Launcher. -The vertical resolution can also be set to a custom value in the Graphics tab of the OpenMW Launcher. +in the Video tab of the Video Panel of the Options menu, or in the Display tab of the launcher. +The vertical resolution can also be set to a custom value in the Display tab of the launcher. window mode ----------- @@ -48,7 +48,7 @@ This setting determines the window mode. This setting can be toggled in game using the dropdown list in the Video tab of the Video panel in the Options menu. -It can also be toggled with the window mode dropdown in the Graphics tab of the OpenMW Launcher. +It can also be toggled with the window mode dropdown in the Display tab of the launcher. screen ------ @@ -63,7 +63,7 @@ since this is the only way to control which screen is used, but it can also be used to control which screen a normal window or a borderless window opens on as well. The screens are numbered in increasing order, beginning with 0. -This setting can be selected from a pull down menu in the Graphics tab of the OpenMW Launcher, +This setting can be selected from a pull down menu in the Display tab of the OpenMW Launcher, but cannot be changed during game play. minimize on focus loss @@ -143,7 +143,7 @@ cannot reach your display's refresh rate. This prevents the input lag from becom Some hardware might not support this mode, in which case traditional vsync will be used. This setting can be adjusted in game using the VSync combo box in the Video tab of the Video panel in the Options menu. -It can also be changed by toggling the Vertical Sync combo box in the Graphics tab of the OpenMW Launcher. +It can also be changed by toggling the Vertical Sync combo box in the Display tab of the launcher. framerate limit --------------- From 6ff14e19d18fe19ff474e2de1f21d05391bde51d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 14 Jan 2024 16:41:55 +0100 Subject: [PATCH 19/47] Make cell models preloading a const operation --- apps/openmw/mwclass/creature.cpp | 11 ++-- apps/openmw/mwclass/creature.hpp | 4 +- apps/openmw/mwclass/creaturelevlist.cpp | 19 ------ apps/openmw/mwclass/creaturelevlist.hpp | 4 -- apps/openmw/mwclass/npc.cpp | 79 +++++++++++++------------ apps/openmw/mwclass/npc.hpp | 4 +- apps/openmw/mwworld/cellpreloader.cpp | 13 +--- apps/openmw/mwworld/cellstore.hpp | 12 ++-- apps/openmw/mwworld/class.cpp | 4 +- apps/openmw/mwworld/class.hpp | 4 +- 10 files changed, 63 insertions(+), 91 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index bb9c1bc277..fbe232093a 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -183,16 +183,17 @@ namespace MWClass return getClassModel(ptr); } - void Creature::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const + void Creature::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const { std::string model = getModel(ptr); if (!model.empty()) models.push_back(model); - // FIXME: use const version of InventoryStore functions once they are available - if (hasInventoryStore(ptr)) + const MWWorld::CustomData* customData = ptr.getRefData().getCustomData(); + if (customData && hasInventoryStore(ptr)) { - const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); + const auto& invStore + = static_cast(*customData->asCreatureCustomData().mContainerStore); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); @@ -513,7 +514,7 @@ namespace MWClass throw std::runtime_error("this creature has no inventory store"); } - bool Creature::hasInventoryStore(const MWWorld::Ptr& ptr) const + bool Creature::hasInventoryStore(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Weapon); } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index b407852242..38b7bb0ec1 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -79,7 +79,7 @@ namespace MWClass MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override; ///< Return inventory store - bool hasInventoryStore(const MWWorld::Ptr& ptr) const override; + bool hasInventoryStore(const MWWorld::ConstPtr& ptr) const override; ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr @@ -107,7 +107,7 @@ namespace MWClass std::string getModel(const MWWorld::ConstPtr& ptr) const override; - void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; + void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< list getModel(). diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index fbae54737c..f16601531d 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -99,25 +99,6 @@ namespace MWClass customData.mSpawn = true; } - void CreatureLevList::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const - { - // disable for now, too many false positives - /* - const MWWorld::LiveCellRef *ref = ptr.get(); - for (std::vector::const_iterator it = ref->mBase->mList.begin(); it != - ref->mBase->mList.end(); ++it) - { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (it->mLevel > player.getClass().getCreatureStats(player).getLevel()) - continue; - - const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - MWWorld::ManualRef ref(store, it->mId); - ref.getPtr().getClass().getModelsToPreload(ref.getPtr(), models); - } - */ - } - void CreatureLevList::insertObjectRendering( const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index d689d1770e..ded8f77de5 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -20,10 +20,6 @@ namespace MWClass bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) - void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; - ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: - ///< list getModel(). - void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4f295f7b35..7ff26fbdb6 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -436,10 +436,11 @@ namespace MWClass return model; } - void Npc::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const + void Npc::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const { const MWWorld::LiveCellRef* npc = ptr.get(); - const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mRace); + const auto& esmStore = MWBase::Environment::get().getESMStore(); + const ESM::Race* race = esmStore->get().search(npc->mBase->mRace); if (race && race->mData.mFlags & ESM::Race::Beast) models.push_back(Settings::models().mBaseanimkna); @@ -453,56 +454,57 @@ namespace MWClass if (!npc->mBase->mHead.empty()) { - const ESM::BodyPart* head - = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mHead); + const ESM::BodyPart* head = esmStore->get().search(npc->mBase->mHead); if (head) models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel)); } if (!npc->mBase->mHair.empty()) { - const ESM::BodyPart* hair - = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mHair); + const ESM::BodyPart* hair = esmStore->get().search(npc->mBase->mHair); if (hair) models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel)); } bool female = (npc->mBase->mFlags & ESM::NPC::Female); - // FIXME: use const version of InventoryStore functions once they are available - // preload equipped items - const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + const MWWorld::CustomData* customData = ptr.getRefData().getCustomData(); + if (customData) { - MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); - if (equipped != invStore.end()) + const MWWorld::InventoryStore& invStore = customData->asNpcCustomData().mInventoryStore; + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { - std::vector parts; - if (equipped->getType() == ESM::Clothing::sRecordId) + MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); + if (equipped != invStore.end()) { - const ESM::Clothing* clothes = equipped->get()->mBase; - parts = clothes->mParts.mParts; - } - else if (equipped->getType() == ESM::Armor::sRecordId) - { - const ESM::Armor* armor = equipped->get()->mBase; - parts = armor->mParts.mParts; - } - else - { - std::string model = equipped->getClass().getModel(*equipped); - if (!model.empty()) - models.push_back(model); - } + const auto addParts = [&](const std::vector& parts) { + for (const ESM::PartReference& partRef : parts) + { + const ESM::RefId& partname + = (female && !partRef.mFemale.empty()) || (!female && partRef.mMale.empty()) + ? partRef.mFemale + : partRef.mMale; - for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) - { - const ESM::RefId& partname - = (female && !it->mFemale.empty()) || (!female && it->mMale.empty()) ? it->mFemale : it->mMale; - - const ESM::BodyPart* part - = MWBase::Environment::get().getESMStore()->get().search(partname); - if (part && !part->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); + const ESM::BodyPart* part = esmStore->get().search(partname); + if (part && !part->mModel.empty()) + models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); + } + }; + if (equipped->getType() == ESM::Clothing::sRecordId) + { + const ESM::Clothing* clothes = equipped->get()->mBase; + addParts(clothes->mParts.mParts); + } + else if (equipped->getType() == ESM::Armor::sRecordId) + { + const ESM::Armor* armor = equipped->get()->mBase; + addParts(armor->mParts.mParts); + } + else + { + std::string model = equipped->getClass().getModel(*equipped); + if (!model.empty()) + models.push_back(model); + } } } } @@ -512,9 +514,8 @@ namespace MWClass { const std::vector& parts = MWRender::NpcAnimation::getBodyParts(race->mId, female, false, false); - for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) + for (const ESM::BodyPart* part : parts) { - const ESM::BodyPart* part = *it; if (part && !part->mModel.empty()) models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index ca0d0ac95d..95245bb994 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -74,7 +74,7 @@ namespace MWClass MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override; ///< Return inventory store - bool hasInventoryStore(const MWWorld::Ptr& ptr) const override { return true; } + bool hasInventoryStore(const MWWorld::ConstPtr& ptr) const override { return true; } bool evaluateHit(const MWWorld::Ptr& ptr, MWWorld::Ptr& victim, osg::Vec3f& hitPosition) const override; @@ -85,7 +85,7 @@ namespace MWClass const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const override; - void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; + void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< list getModel(). diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 7da5e8f848..fe14856364 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -52,20 +52,13 @@ namespace MWWorld struct ListModelsVisitor { - ListModelsVisitor(std::vector& out) - : mOut(out) - { - } - - virtual bool operator()(const MWWorld::Ptr& ptr) + bool operator()(const MWWorld::ConstPtr& ptr) { ptr.getClass().getModelsToPreload(ptr, mOut); return true; } - virtual ~ListModelsVisitor() = default; - std::vector& mOut; }; @@ -90,8 +83,8 @@ namespace MWWorld { mTerrainView = mTerrain->createView(); - ListModelsVisitor visitor(mMeshes); - cell->forEach(visitor); + ListModelsVisitor visitor{ mMeshes }; + cell->forEachConst(visitor); } void abort() override { mAbort = true; } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 0c6527ce22..a8f296045a 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -224,12 +224,12 @@ namespace MWWorld mHasState = true; - for (unsigned int i = 0; i < mMergedRefs.size(); ++i) + for (LiveCellRefBase* mergedRef : mMergedRefs) { - if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef)) + if (!isAccessible(mergedRef->mData, mergedRef->mRef)) continue; - if (!visitor(MWWorld::Ptr(mMergedRefs[i], this))) + if (!visitor(MWWorld::Ptr(mergedRef, this))) return false; } return true; @@ -249,12 +249,12 @@ namespace MWWorld if (mMergedRefsNeedsUpdate) updateMergedRefs(); - for (unsigned int i = 0; i < mMergedRefs.size(); ++i) + for (const LiveCellRefBase* mergedRef : mMergedRefs) { - if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef)) + if (!isAccessible(mergedRef->mData, mergedRef->mRef)) continue; - if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this))) + if (!visitor(MWWorld::ConstPtr(mergedRef, this))) return false; } return true; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index d5062d6add..c8ba337cee 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -149,7 +149,7 @@ namespace MWWorld throw std::runtime_error("class does not have an inventory store"); } - bool Class::hasInventoryStore(const Ptr& ptr) const + bool Class::hasInventoryStore(const ConstPtr& ptr) const { return false; } @@ -320,7 +320,7 @@ namespace MWWorld return false; } - void Class::getModelsToPreload(const Ptr& ptr, std::vector& models) const + void Class::getModelsToPreload(const ConstPtr& ptr, std::vector& models) const { std::string model = getModel(ptr); if (!model.empty()) diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 87e70b3198..694a40cb32 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -170,7 +170,7 @@ namespace MWWorld ///< Return inventory store or throw an exception, if class does not have a /// inventory store (default implementation: throw an exception) - virtual bool hasInventoryStore(const Ptr& ptr) const; + virtual bool hasInventoryStore(const ConstPtr& ptr) const; ///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false) virtual bool canLock(const ConstPtr& ptr) const; @@ -284,7 +284,7 @@ namespace MWWorld virtual bool useAnim() const; ///< Whether or not to use animated variant of model (default false) - virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + virtual void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< list getModel(). From a91e557c68845fdfa66801a4976f1c4c91bed040 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 14 Jan 2024 22:10:18 +0400 Subject: [PATCH 20/47] Fix Touch command (bug 7765) --- CHANGELOG.md | 1 + apps/opencs/model/world/commands.cpp | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75e6f05dd2..e0dbe3960a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,6 +128,7 @@ Bug #7742: Governing attribute training limit should use the modified attribute Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive + Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7770: Sword of the Perithia: Script execution failure Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index da49caef10..b2ad84966f 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -36,7 +36,7 @@ CSMWorld::TouchCommand::TouchCommand(IdTable& table, const std::string& id, QUnd void CSMWorld::TouchCommand::redo() { - mOld.reset(mTable.getRecord(mId).clone().get()); + mOld = mTable.getRecord(mId).clone(); mChanged = mTable.touchRecord(mId); } @@ -181,9 +181,8 @@ const std::string& CSMWorld::TouchLandCommand::getDestinationId() const void CSMWorld::TouchLandCommand::onRedo() { + mOld = mLands.getRecord(mId).clone(); mChanged = mLands.touchRecord(mId); - if (mChanged) - mOld.reset(mLands.getRecord(mId).clone().get()); } void CSMWorld::TouchLandCommand::onUndo() From 645175089016127a252e2172ef5855d0dda499d5 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Jan 2024 02:15:54 +0100 Subject: [PATCH 21/47] Write AiSequence and Script data field by field via decompose function Use the same function to load and save to have single place with field order definition. Use concepts for overload over different types. --- apps/openmw_test_suite/esm3/testsaveload.cpp | 2 - components/esm/decompose.hpp | 10 ++++ components/esm3/aisequence.cpp | 50 +++++++++++++++----- components/esm3/aisequence.hpp | 13 +++-- components/esm3/esmreader.hpp | 12 +++++ components/esm3/esmwriter.hpp | 17 ++++++- components/esm3/loadscpt.cpp | 15 +++--- components/misc/concepts.hpp | 13 +++++ 8 files changed, 104 insertions(+), 28 deletions(-) create mode 100644 components/esm/decompose.hpp create mode 100644 components/misc/concepts.hpp diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 501a0b47c0..f8ef23e887 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -422,7 +422,6 @@ namespace ESM std::copy(std::begin(idle), std::end(idle), record.mData.mIdle); record.mData.mShouldRepeat = 12; record.mDurationData.mRemainingDuration = 13; - record.mDurationData.mUnused = 14; record.mStoredInitialActorPosition = true; constexpr float initialActorPosition[3] = { 15, 16, 17 }; static_assert(std::size(initialActorPosition) == std::size(record.mInitialActorPosition.mValues)); @@ -438,7 +437,6 @@ namespace ESM EXPECT_THAT(result.mData.mIdle, ElementsAreArray(record.mData.mIdle)); EXPECT_EQ(result.mData.mShouldRepeat, record.mData.mShouldRepeat); EXPECT_EQ(result.mDurationData.mRemainingDuration, record.mDurationData.mRemainingDuration); - EXPECT_EQ(result.mDurationData.mUnused, record.mDurationData.mUnused); EXPECT_EQ(result.mStoredInitialActorPosition, record.mStoredInitialActorPosition); EXPECT_THAT(result.mInitialActorPosition.mValues, ElementsAreArray(record.mInitialActorPosition.mValues)); } diff --git a/components/esm/decompose.hpp b/components/esm/decompose.hpp new file mode 100644 index 0000000000..eb6f5070d4 --- /dev/null +++ b/components/esm/decompose.hpp @@ -0,0 +1,10 @@ +#ifndef OPENMW_COMPONENTS_ESM_DECOMPOSE_H +#define OPENMW_COMPONENTS_ESM_DECOMPOSE_H + +namespace ESM +{ + template + void decompose(T&& value, const auto& apply) = delete; +} + +#endif diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index d5b15893bf..c316c2db86 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -3,32 +3,58 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + #include #include namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mDistance, v.mDuration, v.mTimeOfDay, v.mIdle, v.mShouldRepeat); + } + + template T> + void decompose(T&& v, const auto& f) + { + std::uint32_t unused = 0; + f(v.mRemainingDuration, unused); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mX, v.mY, v.mZ); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mX, v.mY, v.mZ, v.mDuration); + } + namespace AiSequence { - void AiWander::load(ESMReader& esm) { - esm.getHNT("DATA", mData.mDistance, mData.mDuration, mData.mTimeOfDay, mData.mIdle, mData.mShouldRepeat); - esm.getHNT("STAR", mDurationData.mRemainingDuration, mDurationData.mUnused); // was mStartTime + esm.getNamedComposite("DATA", mData); + esm.getNamedComposite("STAR", mDurationData); // was mStartTime mStoredInitialActorPosition = esm.getHNOT("POS_", mInitialActorPosition.mValues); } void AiWander::save(ESMWriter& esm) const { - esm.writeHNT("DATA", mData); - esm.writeHNT("STAR", mDurationData); + esm.writeNamedComposite("DATA", mData); + esm.writeNamedComposite("STAR", mDurationData); // was mStartTime if (mStoredInitialActorPosition) - esm.writeHNT("POS_", mInitialActorPosition); + esm.writeHNT("POS_", mInitialActorPosition.mValues); } void AiTravel::load(ESMReader& esm) { - esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ); + esm.getNamedComposite("DATA", mData); esm.getHNT(mHidden, "HIDD"); mRepeat = false; esm.getHNOT(mRepeat, "REPT"); @@ -36,7 +62,7 @@ namespace ESM void AiTravel::save(ESMWriter& esm) const { - esm.writeHNT("DATA", mData); + esm.writeNamedComposite("DATA", mData); esm.writeHNT("HIDD", mHidden); if (mRepeat) esm.writeHNT("REPT", mRepeat); @@ -44,7 +70,7 @@ namespace ESM void AiEscort::load(ESMReader& esm) { - esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ, mData.mDuration); + esm.getNamedComposite("DATA", mData); mTargetId = esm.getHNRefId("TARG"); mTargetActorId = -1; esm.getHNOT(mTargetActorId, "TAID"); @@ -64,7 +90,7 @@ namespace ESM void AiEscort::save(ESMWriter& esm) const { - esm.writeHNT("DATA", mData); + esm.writeNamedComposite("DATA", mData); esm.writeHNRefId("TARG", mTargetId); esm.writeHNT("TAID", mTargetActorId); esm.writeHNT("DURA", mRemainingDuration); @@ -76,7 +102,7 @@ namespace ESM void AiFollow::load(ESMReader& esm) { - esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ, mData.mDuration); + esm.getNamedComposite("DATA", mData); mTargetId = esm.getHNRefId("TARG"); mTargetActorId = -1; esm.getHNOT(mTargetActorId, "TAID"); @@ -101,7 +127,7 @@ namespace ESM void AiFollow::save(ESMWriter& esm) const { - esm.writeHNT("DATA", mData); + esm.writeNamedComposite("DATA", mData); esm.writeHNRefId("TARG", mTargetId); esm.writeHNT("TAID", mTargetActorId); esm.writeHNT("DURA", mRemainingDuration); diff --git a/components/esm3/aisequence.hpp b/components/esm3/aisequence.hpp index 099e5560e1..d6d6259f6b 100644 --- a/components/esm3/aisequence.hpp +++ b/components/esm3/aisequence.hpp @@ -36,32 +36,31 @@ namespace ESM virtual ~AiPackage() {} }; -#pragma pack(push, 1) struct AiWanderData { int16_t mDistance; int16_t mDuration; - unsigned char mTimeOfDay; - unsigned char mIdle[8]; - unsigned char mShouldRepeat; + std::uint8_t mTimeOfDay; + std::uint8_t mIdle[8]; + std::uint8_t mShouldRepeat; }; + struct AiWanderDuration { float mRemainingDuration; - int32_t mUnused; }; + struct AiTravelData { float mX, mY, mZ; }; + struct AiEscortData { float mX, mY, mZ; int16_t mDuration; }; -#pragma pack(pop) - struct AiWander : AiPackage { AiWanderData mData; diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 461f154001..276adf749c 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -12,8 +12,10 @@ #include +#include "components/esm/decompose.hpp" #include "components/esm/esmcommon.hpp" #include "components/esm/refid.hpp" + #include "loadtes3.hpp" namespace ESM @@ -177,6 +179,16 @@ namespace ESM (getT(args), ...); } + void getNamedComposite(NAME name, auto& value) + { + decompose(value, [&](auto&... args) { getHNT(name, args...); }); + } + + void getComposite(auto& value) + { + decompose(value, [&](auto&... args) { (getT(args), ...); }); + } + template >> void skipHT() { diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp index 5086005b1f..101246fe43 100644 --- a/components/esm3/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -5,6 +5,7 @@ #include #include +#include "components/esm/decompose.hpp" #include "components/esm/esmcommon.hpp" #include "components/esm/refid.hpp" @@ -121,6 +122,20 @@ namespace ESM endRecord(name); } + void writeNamedComposite(NAME name, const auto& value) + { + decompose(value, [&](const auto&... args) { + startSubRecord(name); + (writeT(args), ...); + endRecord(name); + }); + } + + void writeComposite(const auto& value) + { + decompose(value, [&](const auto&... args) { (writeT(args), ...); }); + } + // Prevent using writeHNT with strings. This already happened by accident and results in // state being discarded without any error on writing or reading it. :( // writeHNString and friends must be used instead. @@ -132,7 +147,7 @@ namespace ESM void writeHNT(NAME name, const T (&data)[size], int) = delete; template - void writeHNT(NAME name, const T& data, int size) + void writeHNT(NAME name, const T& data, std::size_t size) { startSubRecord(name); writeT(data, size); diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index c1dc759cdc..f79f4989ef 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -4,12 +4,19 @@ #include #include +#include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mNumShorts, v.mNumLongs, v.mNumFloats, v.mScriptDataSize, v.mStringTableSize); + } + void Script::loadSCVR(ESMReader& esm) { uint32_t s = mData.mStringTableSize; @@ -99,11 +106,7 @@ namespace ESM { esm.getSubHeader(); mId = esm.getMaybeFixedRefIdSize(32); - esm.getT(mData.mNumShorts); - esm.getT(mData.mNumLongs); - esm.getT(mData.mNumFloats); - esm.getT(mData.mScriptDataSize); - esm.getT(mData.mStringTableSize); + esm.getComposite(mData); hasHeader = true; break; @@ -157,7 +160,7 @@ namespace ESM esm.startSubRecord("SCHD"); esm.writeMaybeFixedSizeRefId(mId, 32); - esm.writeT(mData, 20); + esm.writeComposite(mData); esm.endRecord("SCHD"); if (isDeleted) diff --git a/components/misc/concepts.hpp b/components/misc/concepts.hpp new file mode 100644 index 0000000000..d8573e94ab --- /dev/null +++ b/components/misc/concepts.hpp @@ -0,0 +1,13 @@ +#ifndef OPENMW_COMPONENTS_MISC_CONCEPTS_H +#define OPENMW_COMPONENTS_MISC_CONCEPTS_H + +#include +#include + +namespace Misc +{ + template + concept SameAsWithoutCvref = std::same_as, std::remove_cvref_t>; +} + +#endif From 4d6350539c860aa9c0711d79b338f4d87ad44f2d Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Dec 2023 12:36:23 +0100 Subject: [PATCH 22/47] Move FindLowestUnusedTexUnitVisitor to unnamed namespace It's not used anywhere except this translation unit so no need to make the symbol available everywhere else. --- components/sceneutil/util.cpp | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index b71de4c2a3..ce48702a74 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -37,27 +37,27 @@ namespace SceneUtil } const std::array glowTextureNames = generateGlowTextureNames(); + + struct FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor + { + FindLowestUnusedTexUnitVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + void apply(osg::Node& node) override + { + if (osg::StateSet* stateset = node.getStateSet()) + mLowestUnusedTexUnit + = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); + + traverse(node); + } + + int mLowestUnusedTexUnit = 0; + }; } - class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor - { - public: - FindLowestUnusedTexUnitVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mLowestUnusedTexUnit(0) - { - } - - void apply(osg::Node& node) override - { - if (osg::StateSet* stateset = node.getStateSet()) - mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); - - traverse(node); - } - int mLowestUnusedTexUnit; - }; - GlowUpdater::GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector>& textures, osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem) From a2147d70cc45d60a4f2d048980a5bcd5496d4b55 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 16 Jan 2024 00:30:41 +0100 Subject: [PATCH 23/47] Use forward declaration for some VFS types This will allow to save on preprocessed code size in the future changes. --- apps/niftest/niftest.cpp | 1 + apps/opencs/model/world/resources.cpp | 1 + apps/openmw/mwgui/loadingscreen.cpp | 1 + apps/openmw/mwgui/settingswindow.cpp | 1 + apps/openmw/mwlua/vfsbindings.cpp | 1 + apps/openmw/mwrender/animation.cpp | 1 + apps/openmw/mwrender/postprocessor.cpp | 1 + apps/openmw/mwsound/soundmanagerimp.cpp | 1 + apps/openmw_test_suite/testing_util.hpp | 1 + components/vfs/archive.hpp | 22 ++------ components/vfs/bsaarchive.hpp | 1 + components/vfs/file.hpp | 21 ++++++++ components/vfs/filemap.hpp | 14 +++++ components/vfs/filesystemarchive.hpp | 3 +- components/vfs/manager.cpp | 9 +++- components/vfs/manager.hpp | 49 +++-------------- components/vfs/recursivedirectoryiterator.hpp | 53 +++++++++++++++++++ 17 files changed, 120 insertions(+), 61 deletions(-) create mode 100644 components/vfs/file.hpp create mode 100644 components/vfs/filemap.hpp create mode 100644 components/vfs/recursivedirectoryiterator.hpp diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 29488fb677..32fd65c348 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index bfab0193b0..345f6008ec 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -11,6 +11,7 @@ #include #include +#include CSMWorld::Resources::Resources( const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, const char* const* extensions) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 1723841b32..8ba2bb8312 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index fbd54586df..0ffadc43a3 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index 0eccb336c2..c9b1a45fe2 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 7fa8e43c37..feed9719b6 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 1aaeb460b7..c31f49f35a 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 64f8959218..383d316c91 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index 0c941053a7..aa76f7f944 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index 79c876b391..42b88219d7 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -1,27 +1,13 @@ -#ifndef OPENMW_COMPONENTS_RESOURCE_ARCHIVE_H -#define OPENMW_COMPONENTS_RESOURCE_ARCHIVE_H +#ifndef OPENMW_COMPONENTS_VFS_ARCHIVE_H +#define OPENMW_COMPONENTS_VFS_ARCHIVE_H -#include -#include +#include #include -#include +#include "filemap.hpp" namespace VFS { - - class File - { - public: - virtual ~File() = default; - - virtual Files::IStreamPtr open() = 0; - - virtual std::filesystem::path getPath() = 0; - }; - - using FileMap = std::map>; - class Archive { public: diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 29098db45d..304fc438ad 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -2,6 +2,7 @@ #define VFS_BSAARCHIVE_HPP_ #include "archive.hpp" +#include "file.hpp" #include "pathutil.hpp" #include diff --git a/components/vfs/file.hpp b/components/vfs/file.hpp new file mode 100644 index 0000000000..f2dadb1162 --- /dev/null +++ b/components/vfs/file.hpp @@ -0,0 +1,21 @@ +#ifndef OPENMW_COMPONENTS_VFS_FILE_H +#define OPENMW_COMPONENTS_VFS_FILE_H + +#include + +#include + +namespace VFS +{ + class File + { + public: + virtual ~File() = default; + + virtual Files::IStreamPtr open() = 0; + + virtual std::filesystem::path getPath() = 0; + }; +} + +#endif diff --git a/components/vfs/filemap.hpp b/components/vfs/filemap.hpp new file mode 100644 index 0000000000..808153fc05 --- /dev/null +++ b/components/vfs/filemap.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_VFS_FILEMAP_H +#define OPENMW_COMPONENTS_VFS_FILEMAP_H + +#include +#include + +namespace VFS +{ + class File; + + using FileMap = std::map>; +} + +#endif diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index e31ef9bd30..00fe5ba971 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -2,8 +2,9 @@ #define OPENMW_COMPONENTS_RESOURCE_FILESYSTEMARCHIVE_H #include "archive.hpp" -#include +#include "file.hpp" +#include #include namespace VFS diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index cc231847f5..5315f17252 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -5,12 +5,19 @@ #include #include +#include #include "archive.hpp" +#include "file.hpp" #include "pathutil.hpp" +#include "recursivedirectoryiterator.hpp" namespace VFS { + Manager::Manager() = default; + + Manager::~Manager() = default; + void Manager::reset() { mIndex.clear(); @@ -70,7 +77,7 @@ namespace VFS return found->second->getPath(); } - Manager::RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(std::string_view path) const + RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(std::string_view path) const { if (path.empty()) return { mIndex.begin(), mIndex.end() }; diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 76405aae2c..05990a8607 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -4,32 +4,17 @@ #include #include -#include #include #include +#include #include -#include "archive.hpp" +#include "filemap.hpp" namespace VFS { - - template - class IteratorPair - { - public: - IteratorPair(Iterator first, Iterator last) - : mFirst(first) - , mLast(last) - { - } - Iterator begin() const { return mFirst; } - Iterator end() const { return mLast; } - - private: - Iterator mFirst; - Iterator mLast; - }; + class Archive; + class RecursiveDirectoryRange; /// @brief The main class responsible for loading files from a virtual file system. /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) @@ -38,29 +23,11 @@ namespace VFS /// @par Most of the methods in this class are considered thread-safe, see each method documentation for details. class Manager { - class RecursiveDirectoryIterator - { - public: - RecursiveDirectoryIterator(FileMap::const_iterator it) - : mIt(it) - { - } - const std::string& operator*() const { return mIt->first; } - const std::string* operator->() const { return &mIt->first; } - bool operator!=(const RecursiveDirectoryIterator& other) { return mIt != other.mIt; } - RecursiveDirectoryIterator& operator++() - { - ++mIt; - return *this; - } - - private: - FileMap::const_iterator mIt; - }; - - using RecursiveDirectoryRange = IteratorPair; - public: + Manager(); + + ~Manager(); + // Empty the file index and unregister archives. void reset(); diff --git a/components/vfs/recursivedirectoryiterator.hpp b/components/vfs/recursivedirectoryiterator.hpp new file mode 100644 index 0000000000..82f8e594fd --- /dev/null +++ b/components/vfs/recursivedirectoryiterator.hpp @@ -0,0 +1,53 @@ +#ifndef OPENMW_COMPONENTS_VFS_RECURSIVEDIRECTORYITERATOR_H +#define OPENMW_COMPONENTS_VFS_RECURSIVEDIRECTORYITERATOR_H + +#include + +#include "filemap.hpp" + +namespace VFS +{ + class RecursiveDirectoryIterator + { + public: + RecursiveDirectoryIterator(FileMap::const_iterator it) + : mIt(it) + { + } + + const std::string& operator*() const { return mIt->first; } + + const std::string* operator->() const { return &mIt->first; } + + RecursiveDirectoryIterator& operator++() + { + ++mIt; + return *this; + } + + friend bool operator==(const RecursiveDirectoryIterator& lhs, const RecursiveDirectoryIterator& rhs) = default; + + private: + FileMap::const_iterator mIt; + }; + + class RecursiveDirectoryRange + { + public: + RecursiveDirectoryRange(RecursiveDirectoryIterator first, RecursiveDirectoryIterator last) + : mBegin(first) + , mEnd(last) + { + } + + RecursiveDirectoryIterator begin() const { return mBegin; } + + RecursiveDirectoryIterator end() const { return mEnd; } + + private: + RecursiveDirectoryIterator mBegin; + RecursiveDirectoryIterator mEnd; + }; +} + +#endif From a340b49cbc51455ff38910f3a941a3ca2d5f4fb6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 16 Jan 2024 10:23:13 +0400 Subject: [PATCH 24/47] Enhance light settings tweaking --- apps/launcher/settingspage.cpp | 54 +++++-- apps/launcher/settingspage.hpp | 1 + apps/launcher/ui/settingspage.ui | 138 +++++++++++++++++- apps/openmw/mwgui/settingswindow.cpp | 2 +- files/data/l10n/OMWEngine/de.yaml | 4 + files/data/l10n/OMWEngine/en.yaml | 4 + files/data/l10n/OMWEngine/fr.yaml | 4 + files/data/l10n/OMWEngine/ru.yaml | 4 + files/data/l10n/OMWEngine/sv.yaml | 4 + .../data/mygui/openmw_settings_window.layout | 9 +- 10 files changed, 200 insertions(+), 24 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 9492326cb0..c274b75f79 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -213,21 +213,6 @@ bool Launcher::SettingsPage::loadSettings() loadSettingBool(Settings::fog().mSkyBlending, *skyBlendingCheckBox); skyBlendingStartComboBox->setValue(Settings::fog().mSkyBlendingStart); - int lightingMethod = 1; - switch (Settings::shaders().mLightingMethod) - { - case SceneUtil::LightingMethod::FFP: - lightingMethod = 0; - break; - case SceneUtil::LightingMethod::PerObjectUniform: - lightingMethod = 1; - break; - case SceneUtil::LightingMethod::SingleUBO: - lightingMethod = 2; - break; - } - lightingMethodComboBox->setCurrentIndex(lightingMethod); - loadSettingBool(Settings::shadows().mActorShadows, *actorShadowsCheckBox); loadSettingBool(Settings::shadows().mPlayerShadows, *playerShadowsCheckBox); loadSettingBool(Settings::shadows().mTerrainShadows, *terrainShadowsCheckBox); @@ -261,6 +246,31 @@ bool Launcher::SettingsPage::loadSettings() shadowResolutionComboBox->setCurrentIndex(shadowResIndex); connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotShadowDistLimitToggled); + + lightsMaxLightsSpinBox->setValue(Settings::shaders().mMaxLights); + lightsMaximumDistanceSpinBox->setValue(Settings::shaders().mMaximumLightDistance); + lightFadeMultiplierSpinBox->setValue(Settings::shaders().mLightFadeStart); + lightsBoundingSphereMultiplierSpinBox->setValue(Settings::shaders().mLightBoundsMultiplier); + lightsMinimumInteriorBrightnessSpinBox->setValue(Settings::shaders().mMinimumInteriorBrightness); + + connect(lightingMethodComboBox, qOverload(&QComboBox::currentIndexChanged), this, + &SettingsPage::slotLightTypeCurrentIndexChanged); + + int lightingMethod = 1; + switch (Settings::shaders().mLightingMethod) + { + case SceneUtil::LightingMethod::FFP: + lightingMethod = 0; + break; + case SceneUtil::LightingMethod::PerObjectUniform: + lightingMethod = 1; + break; + case SceneUtil::LightingMethod::SingleUBO: + lightingMethod = 2; + break; + } + lightingMethodComboBox->setCurrentIndex(lightingMethod); + slotLightTypeCurrentIndexChanged(lightingMethod); } // Audio @@ -454,6 +464,12 @@ void Launcher::SettingsPage::saveSettings() Settings::shadows().mComputeSceneBounds.set("primitives"); else Settings::shadows().mComputeSceneBounds.set("none"); + + Settings::shaders().mMaxLights.set(lightsMaxLightsSpinBox->value()); + Settings::shaders().mMaximumLightDistance.set(lightsMaximumDistanceSpinBox->value()); + Settings::shaders().mLightFadeStart.set(lightFadeMultiplierSpinBox->value()); + Settings::shaders().mLightBoundsMultiplier.set(lightsBoundingSphereMultiplierSpinBox->value()); + Settings::shaders().mMinimumInteriorBrightness.set(lightsMinimumInteriorBrightnessSpinBox->value()); } // Audio @@ -562,3 +578,11 @@ void Launcher::SettingsPage::slotShadowDistLimitToggled(bool checked) shadowDistanceSpinBox->setEnabled(checked); fadeStartSpinBox->setEnabled(checked); } + +void Launcher::SettingsPage::slotLightTypeCurrentIndexChanged(int index) +{ + lightsMaximumDistanceSpinBox->setEnabled(index != 0); + lightsMaxLightsSpinBox->setEnabled(index != 0); + lightsBoundingSphereMultiplierSpinBox->setEnabled(index != 0); + lightsMinimumInteriorBrightnessSpinBox->setEnabled(index != 0); +} diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index a8a6b7c26d..ea675857ea 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -33,6 +33,7 @@ namespace Launcher void slotPostProcessToggled(bool checked); void slotSkyBlendingToggled(bool checked); void slotShadowDistLimitToggled(bool checked); + void slotLightTypeCurrentIndexChanged(int index); private: Config::GameSettings& mGameSettings; diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index cf8215ef30..7006238e71 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1018,7 +1018,17 @@ - + + + + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + + + Lights maximum distance + + + + Qt::Vertical @@ -1031,29 +1041,143 @@ + + + + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + + + Max light sources + + + + + + + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + + + Lights fade multiplier + + + + + + + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + + + Lighting method + + + - legacy + Legacy - shaders compatibility + Shaders (compatibility) - shaders + Shaders - - + + + + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + - Lighting method + Lights bounding sphere multiplier + + + + + + + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + + + Lights minimum interior brightness + + + + + + + 5.000000000000000 + + + 0.050000000000000 + + + 1.650000000000000 + + + + + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.850000000000000 + + + + + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.080000000000000 + + + + + + + unit(s) + + + 8192 + + + 128 + + + 8192 + + + + + + + 2 + + + 64 + + + 2 diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index fbd54586df..6a1cfaefe2 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -131,7 +131,7 @@ namespace void updateMaxLightsComboBox(MyGUI::ComboBox* box) { constexpr int min = 8; - constexpr int max = 32; + constexpr int max = 64; constexpr int increment = 8; const int maxLights = Settings::shaders().mMaxLights; // show increments of 8 in dropdown diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index 0f729d0077..2874001309 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -87,6 +87,10 @@ LightsBoundingSphereMultiplier: "Bounding-Sphere-Multiplikator" LightsBoundingSphereMultiplierTooltip: "Standard: 1.65\nMultiplikator für Begrenzungskugel.\nHöhere Zahlen ermöglichen einen sanften Abfall, erfordern jedoch eine Erhöhung der Anzahl der maximalen Lichter.\n\nBeeinflusst nicht die Beleuchtung oder Lichtstärke." LightsFadeStartMultiplier: "Licht Verblassungs-Start-Multiplikator" LightsFadeStartMultiplierTooltip: "Standard: 0.85\nBruchteil der maximalen Entfernung, bei der die Lichter zu verblassen beginnen.\n\nStellen Sie hier einen niedrigen Wert für langsamere Übergänge oder einen hohen Wert für schnellere Übergänge ein." +#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n +# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n +# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n +# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." LightsMaximumDistance: "Maximale Lichtreichweite" LightsMaximumDistanceTooltip: "Standard: 8192\nMaximale Entfernung, bei der Lichter erscheinen (gemessen in Einheiten).\n\nSetzen Sie dies auf 0, um eine unbegrenzte Entfernung zu verwenden." LightsMinimumInteriorBrightness: "Minimale Innenhelligkeit" diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index 09db2b496d..0455d11e07 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -107,6 +107,10 @@ LightsBoundingSphereMultiplier: "Bounding Sphere Multiplier" LightsBoundingSphereMultiplierTooltip: "Default: 1.65\nMultipler for bounding sphere of lights.\nHigher numbers allows for smooth falloff but require an increase in number of max lights.\n\nDoes not effect the illumination or strength of lights." LightsFadeStartMultiplier: "Fade Start Multiplier" LightsFadeStartMultiplierTooltip: "Default: 0.85\nFraction of maximum distance at which lights will start to fade.\n\nSet this to a low value for slower transitions or a high value for quicker transitions." +LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n + \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n + \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n + \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." LightsMaximumDistance: "Maximum Light Distance" LightsMaximumDistanceTooltip: "Default: 8192\nMaximum distance at which lights will appear (measured in units).\n\nSet this to 0 to use an unlimited distance." LightsMinimumInteriorBrightness: "Minimum Interior Brightness" diff --git a/files/data/l10n/OMWEngine/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml index f2772b017e..85bac08612 100644 --- a/files/data/l10n/OMWEngine/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -107,6 +107,10 @@ LightsBoundingSphereMultiplier: "Multiplicateur de sphère englobante" LightsBoundingSphereMultiplierTooltip: "valeur par défaut: 1.65\nMultiplicateur pour le rayon de la sphère incluant les sources lumineuses.\nUn multiplicateur plus élevé permet une extinction plus douce, mais applique un plus grand nombre de sources lumineuses sur chaque objet.\n\nCe paramètre ne modifie ni l'intensité ni la luminance des lumières." LightsFadeStartMultiplier: "Seuil de perte d'éclat lumineux" LightsFadeStartMultiplierTooltip: "valeur par défaut: 0.85\nFraction de la distance maximale d'une source à partir de laquelle l'intensité lumineuse commence à décroître.\n\nSélectionnez une valeur basse pour une transition douce ou une valeur plus élevée pour une transition plus abrupte." +#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n +# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n +# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n +# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." LightsMaximumDistance: "Distance maximale des sources lumineuses" LightsMaximumDistanceTooltip: "valeur par défaut: 8192\nDistance maximale d'affichage des sources lumineuses (en unité de distance).\n\nMettez cette valeur à 0 pour une distance d'affichage infinie." LightsMinimumInteriorBrightness: "Luminosité intérieure minimale" diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index 2bcb76a442..8d221fe33c 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -107,6 +107,10 @@ LightsBoundingSphereMultiplier: "Множитель размера ограни LightsBoundingSphereMultiplierTooltip: "Значение по умолчанию: 1.65\nМножитель размера ограничивающей сферы источников света.\nВысокие значения делают затухание света плавнее, но требуют более высокого максимального количества источников света.\n\nНастройка не влияет на уровень освещения или мощность источников света." LightsFadeStartMultiplier: "Множитель начала затухания" LightsFadeStartMultiplierTooltip: "Значение по умолчанию: 0.85\nДоля расстояния (относительно дальности отображения источников света), на которой свет начинает затухать.\n\nНизкие значения ведут к плавному затуханию, высокие - к резкому." +LightsLightingMethodTooltip: "Задает способ обработки источников света.\n\n + \"Устаревший\" всегда использует 8 источников света на объект и выдает освещение, наиболее близкое к таковому в оригинальной игре.\n\n + \"Шейдеры (режим совместимости)\" убирает ограничение в 8 источников света. Этот режим также позволяет освещению влиять на анимированную траву и позволяет настроить угасание света на расстоянии. Рекомендуется использовать этот режим на устаревшем аппаратном обеспечении и с количеством источников света на объект около 8.\n\n + \"Шейдеры\" работает аналогично режиму \"Шейдеры (режим совместимости)\", но использует более современный подход, позволяющий использовать большее количество источников света с минимальным влиянием на производительность на современном аппаратном обеспечении." LightsMaximumDistance: "Дальность отображения источников света" LightsMaximumDistanceTooltip: "Значение по умолчанию: 8192\nМаксимальное расстояние, на котором будут отображаться источники света (во внутриигровых единицах измерения).\n\nЕсли 0, то расстояние не ограничено." LightsMinimumInteriorBrightness: "Минимальный уровень освещения в помещениях" diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index dc65726fdd..134fab0e95 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -107,6 +107,10 @@ LightsBoundingSphereMultiplier: "Gränssfärsmultiplikator" LightsBoundingSphereMultiplierTooltip: "Förvalt: 1.65\nMultiplikator för ljusens gränssfär.\nHögre värden ger mjukare minskning av gränssfären, men kräver högre värde i Max antal ljuskällor.\n\nPåverkar inte ljusstyrkan." LightsFadeStartMultiplier: "Blekningsstartmultiplikator" LightsFadeStartMultiplierTooltip: "Förvalt: 0.85\nFraktion av det maximala avståndet från vilket ljuskällor börjar blekna.\n\nVälj lågt värde för långsammare övergång eller högre värde för snabbare övergång." +#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n +# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n +# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n +# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." LightsMaximumDistance: "Maximalt ljusavstånd" LightsMaximumDistanceTooltip: "Förvalt: 8192\nMaximala avståndet där ljuskällor syns (mätt i enheter).\n\nVärdet 0 ger oändligt avstånd." LightsMinimumInteriorBrightness: "Minsta ljusstyrka i interiörer" diff --git a/files/data/mygui/openmw_settings_window.layout b/files/data/mygui/openmw_settings_window.layout index e912ababfd..27298b9756 100644 --- a/files/data/mygui/openmw_settings_window.layout +++ b/files/data/mygui/openmw_settings_window.layout @@ -536,6 +536,9 @@ + + + @@ -561,6 +564,10 @@ + + + + @@ -570,7 +577,7 @@ - + From 27fa411f4f701985079da35f91c22c54a3626fe8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 16 Jan 2024 20:56:58 +0100 Subject: [PATCH 25/47] Convert strings in nif files to utf8 --- apps/bulletobjecttool/main.cpp | 2 +- apps/navmeshtool/main.cpp | 2 +- apps/niftest/niftest.cpp | 2 +- apps/opencs/model/world/data.cpp | 2 +- apps/openmw/engine.cpp | 3 ++- components/nif/niffile.cpp | 5 +++-- components/nif/niffile.hpp | 8 +++++++- components/nif/nifstream.cpp | 4 ++++ components/nif/nifstream.hpp | 11 ++++++++++- components/resource/keyframemanager.cpp | 6 ++++-- components/resource/keyframemanager.hpp | 9 ++++++++- components/resource/niffilemanager.cpp | 9 +++++---- components/resource/niffilemanager.hpp | 9 ++++++++- components/resource/resourcesystem.cpp | 6 +++--- components/resource/resourcesystem.hpp | 7 ++++++- 15 files changed, 64 insertions(+), 21 deletions(-) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 7d87899f4a..2165f93804 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -174,7 +174,7 @@ namespace constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); - Resource::NifFileManager nifFileManager(&vfs); + Resource::NifFileManager nifFileManager(&vfs, &encoder); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 8604bcdfb0..a9c30cf23e 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -221,7 +221,7 @@ namespace NavMeshTool constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); - Resource::NifFileManager nifFileManager(&vfs); + Resource::NifFileManager nifFileManager(&vfs, &encoder); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); DetourNavigator::RecastGlobalAllocator::init(); diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 29488fb677..5d0f723ee5 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -84,7 +84,7 @@ void readNIF( try { Nif::NIFFile file(fullPath); - Nif::Reader reader(file); + Nif::Reader reader(file, nullptr); if (vfs != nullptr) reader.parse(vfs->get(pathStr)); else diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 6322a77e66..ef2e289ee2 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -149,7 +149,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mResourcesManager.setVFS(mVFS.get()); constexpr double expiryDelay = 0; - mResourceSystem = std::make_unique(mVFS.get(), expiryDelay); + mResourceSystem = std::make_unique(mVFS.get(), expiryDelay, &mEncoder); Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 92483bd8c3..c2e57208b4 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -706,7 +706,8 @@ void OMW::Engine::prepareEngine() VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); - mResourceSystem = std::make_unique(mVFS.get(), Settings::cells().mCacheExpiryDelay); + mResourceSystem + = std::make_unique(mVFS.get(), Settings::cells().mCacheExpiryDelay, mEncoder.get()); mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits); mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply( false); // keep to Off for now to allow better state sharing diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index d6d063a254..7a38b0dc1a 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -24,7 +24,7 @@ namespace Nif { - Reader::Reader(NIFFile& file) + Reader::Reader(NIFFile& file, const ToUTF8::Utf8Encoder* encoder) : mVersion(file.mVersion) , mUserVersion(file.mUserVersion) , mBethVersion(file.mBethVersion) @@ -33,6 +33,7 @@ namespace Nif , mRecords(file.mRecords) , mRoots(file.mRoots) , mUseSkinning(file.mUseSkinning) + , mEncoder(encoder) { } @@ -519,7 +520,7 @@ namespace Nif const std::array fileHash = Files::getHash(mFilename, *stream); mHash.append(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); - NIFStream nif(*this, std::move(stream)); + NIFStream nif(*this, std::move(stream), mEncoder); // Check the header string std::string head = nif.getVersionString(); diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 6f0030af47..38b0712373 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -11,6 +11,11 @@ #include "record.hpp" +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace Nif { @@ -112,6 +117,7 @@ namespace Nif std::vector mStrings; bool& mUseSkinning; + const ToUTF8::Utf8Encoder* mEncoder; static std::atomic_bool sLoadUnsupportedFiles; static std::atomic_bool sWriteNifDebugLog; @@ -122,7 +128,7 @@ namespace Nif public: /// Open a NIF stream. The name is used for error messages. - explicit Reader(NIFFile& file); + Reader(NIFFile& file, const ToUTF8::Utf8Encoder* encoder); /// Parse the file void parse(Files::IStreamPtr&& stream); diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 2eba746ccf..93714e37f0 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -4,6 +4,8 @@ #include "niffile.hpp" +#include "../to_utf8/to_utf8.hpp" + namespace { @@ -58,6 +60,8 @@ namespace Nif size_t end = str.find('\0'); if (end != std::string::npos) str.erase(end); + if (mEncoder) + str = mEncoder->getStatelessEncoder().getUtf8(str, ToUTF8::BufferAllocationPolicy::UseGrowFactor, mBuffer); return str; } diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 95205c4fda..958aef7254 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,11 @@ #include "niftypes.hpp" +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace Nif { @@ -67,11 +73,14 @@ namespace Nif { const Reader& mReader; Files::IStreamPtr mStream; + const ToUTF8::Utf8Encoder* mEncoder; + std::string mBuffer; public: - explicit NIFStream(const Reader& reader, Files::IStreamPtr&& stream) + explicit NIFStream(const Reader& reader, Files::IStreamPtr&& stream, const ToUTF8::Utf8Encoder* encoder) : mReader(reader) , mStream(std::move(stream)) + , mEncoder(encoder) { } diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 574d761d09..6895a0238f 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -207,9 +207,11 @@ namespace Resource namespace Resource { - KeyframeManager::KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay) + KeyframeManager::KeyframeManager( + const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay, const ToUTF8::Utf8Encoder* encoder) : ResourceManager(vfs, expiryDelay) , mSceneManager(sceneManager) + , mEncoder(encoder) { } @@ -226,7 +228,7 @@ namespace Resource if (Misc::getFileExtension(normalized) == "kf") { auto file = std::make_shared(normalized); - Nif::Reader reader(*file); + Nif::Reader reader(*file, mEncoder); reader.parse(mVFS->getNormalized(normalized)); NifOsg::Loader::loadKf(*file, *loaded.get()); } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 0c92553949..f684e22ee7 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -9,6 +9,11 @@ #include "resourcemanager.hpp" +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace Resource { /// @brief extract animations from OSG formats to OpenMW's animation system @@ -48,7 +53,8 @@ namespace Resource class KeyframeManager : public ResourceManager { public: - explicit KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay); + explicit KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay, + const ToUTF8::Utf8Encoder* encoder); ~KeyframeManager() = default; /// Retrieve a read-only keyframe resource by name (case-insensitive). @@ -59,6 +65,7 @@ namespace Resource private: SceneManager* mSceneManager; + const ToUTF8::Utf8Encoder* mEncoder; }; } diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 5e457cdfaa..fb57bc8c85 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -24,21 +24,22 @@ namespace Resource { } - NifFileHolder() {} + NifFileHolder() = default; META_Object(Resource, NifFileHolder) Nif::NIFFilePtr mNifFile; }; - NifFileManager::NifFileManager(const VFS::Manager* vfs) + NifFileManager::NifFileManager(const VFS::Manager* vfs, const ToUTF8::Utf8Encoder* encoder) // NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager, // so no point in using an expiry delay. : ResourceManager(vfs, 0) + , mEncoder(encoder) { } - NifFileManager::~NifFileManager() {} + NifFileManager::~NifFileManager() = default; Nif::NIFFilePtr NifFileManager::get(const std::string& name) { @@ -48,7 +49,7 @@ namespace Resource else { auto file = std::make_shared(name); - Nif::Reader reader(*file); + Nif::Reader reader(*file, mEncoder); reader.parse(mVFS->get(name)); obj = new NifFileHolder(file); mCache->addEntryToObjectCache(name, obj); diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index 5aef3f3016..2225a9fd4c 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -5,6 +5,11 @@ #include "resourcemanager.hpp" +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace Resource { @@ -12,8 +17,10 @@ namespace Resource /// @note May be used from any thread. class NifFileManager : public ResourceManager { + const ToUTF8::Utf8Encoder* mEncoder; + public: - NifFileManager(const VFS::Manager* vfs); + NifFileManager(const VFS::Manager* vfs, const ToUTF8::Utf8Encoder* encoder); ~NifFileManager(); /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 0bee08e9ac..7d704e7d1e 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -10,13 +10,13 @@ namespace Resource { - ResourceSystem::ResourceSystem(const VFS::Manager* vfs, double expiryDelay) + ResourceSystem::ResourceSystem(const VFS::Manager* vfs, double expiryDelay, const ToUTF8::Utf8Encoder* encoder) : mVFS(vfs) { - mNifFileManager = std::make_unique(vfs); + mNifFileManager = std::make_unique(vfs, encoder); mImageManager = std::make_unique(vfs, expiryDelay); mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), expiryDelay); - mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay); + mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay, encoder); addResourceManager(mNifFileManager.get()); addResourceManager(mKeyframeManager.get()); diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index d06ac79640..554083852e 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -15,6 +15,11 @@ namespace osg class State; } +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace Resource { @@ -30,7 +35,7 @@ namespace Resource class ResourceSystem { public: - explicit ResourceSystem(const VFS::Manager* vfs, double expiryDelay); + explicit ResourceSystem(const VFS::Manager* vfs, double expiryDelay, const ToUTF8::Utf8Encoder* encoder); ~ResourceSystem(); SceneManager* getSceneManager(); From 48db113149b1219073a4a7a43393292b53b96339 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 17 Jan 2024 18:10:42 +0100 Subject: [PATCH 26/47] Address feedback --- CHANGELOG.md | 1 + apps/bulletobjecttool/main.cpp | 2 +- apps/navmeshtool/main.cpp | 2 +- apps/opencs/model/world/data.cpp | 3 ++- apps/openmw/engine.cpp | 4 ++-- components/nif/niffile.cpp | 2 +- components/nif/niffile.hpp | 6 +++--- components/nif/nifstream.cpp | 2 +- components/nif/nifstream.hpp | 7 ++++--- components/resource/keyframemanager.cpp | 4 ++-- components/resource/keyframemanager.hpp | 6 +++--- components/resource/niffilemanager.cpp | 2 +- components/resource/niffilemanager.hpp | 6 +++--- components/resource/resourcesystem.cpp | 3 ++- components/resource/resourcesystem.hpp | 5 +++-- components/to_utf8/to_utf8.hpp | 2 +- 16 files changed, 31 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75e6f05dd2..a35f44fe2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,6 +129,7 @@ Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive Bug #7770: Sword of the Perithia: Script execution failure + Bug #7780: Non-ASCII texture paths in NIF files don't work Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 2165f93804..504aef7e67 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -174,7 +174,7 @@ namespace constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); - Resource::NifFileManager nifFileManager(&vfs, &encoder); + Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index a9c30cf23e..9ed7fb4c2e 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -221,7 +221,7 @@ namespace NavMeshTool constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); - Resource::NifFileManager nifFileManager(&vfs, &encoder); + Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); DetourNavigator::RecastGlobalAllocator::init(); diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ef2e289ee2..428ffb2737 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -149,7 +149,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mResourcesManager.setVFS(mVFS.get()); constexpr double expiryDelay = 0; - mResourceSystem = std::make_unique(mVFS.get(), expiryDelay, &mEncoder); + mResourceSystem + = std::make_unique(mVFS.get(), expiryDelay, &mEncoder.getStatelessEncoder()); Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index c2e57208b4..5b8d725583 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -706,8 +706,8 @@ void OMW::Engine::prepareEngine() VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); - mResourceSystem - = std::make_unique(mVFS.get(), Settings::cells().mCacheExpiryDelay, mEncoder.get()); + mResourceSystem = std::make_unique( + mVFS.get(), Settings::cells().mCacheExpiryDelay, &mEncoder.get()->getStatelessEncoder()); mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits); mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply( false); // keep to Off for now to allow better state sharing diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 7a38b0dc1a..74c3b391a1 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -24,7 +24,7 @@ namespace Nif { - Reader::Reader(NIFFile& file, const ToUTF8::Utf8Encoder* encoder) + Reader::Reader(NIFFile& file, const ToUTF8::StatelessUtf8Encoder* encoder) : mVersion(file.mVersion) , mUserVersion(file.mUserVersion) , mBethVersion(file.mBethVersion) diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 38b0712373..993e9b7eea 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -13,7 +13,7 @@ namespace ToUTF8 { - class Utf8Encoder; + class StatelessUtf8Encoder; } namespace Nif @@ -117,7 +117,7 @@ namespace Nif std::vector mStrings; bool& mUseSkinning; - const ToUTF8::Utf8Encoder* mEncoder; + const ToUTF8::StatelessUtf8Encoder* mEncoder; static std::atomic_bool sLoadUnsupportedFiles; static std::atomic_bool sWriteNifDebugLog; @@ -128,7 +128,7 @@ namespace Nif public: /// Open a NIF stream. The name is used for error messages. - Reader(NIFFile& file, const ToUTF8::Utf8Encoder* encoder); + explicit Reader(NIFFile& file, const ToUTF8::StatelessUtf8Encoder* encoder); /// Parse the file void parse(Files::IStreamPtr&& stream); diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 93714e37f0..f960e8d972 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -61,7 +61,7 @@ namespace Nif if (end != std::string::npos) str.erase(end); if (mEncoder) - str = mEncoder->getStatelessEncoder().getUtf8(str, ToUTF8::BufferAllocationPolicy::UseGrowFactor, mBuffer); + str = mEncoder->getUtf8(str, ToUTF8::BufferAllocationPolicy::UseGrowFactor, mBuffer); return str; } diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 958aef7254..062f7c6512 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -25,7 +25,7 @@ namespace ToUTF8 { - class Utf8Encoder; + class StatelessUtf8Encoder; } namespace Nif @@ -73,11 +73,12 @@ namespace Nif { const Reader& mReader; Files::IStreamPtr mStream; - const ToUTF8::Utf8Encoder* mEncoder; + const ToUTF8::StatelessUtf8Encoder* mEncoder; std::string mBuffer; public: - explicit NIFStream(const Reader& reader, Files::IStreamPtr&& stream, const ToUTF8::Utf8Encoder* encoder) + explicit NIFStream( + const Reader& reader, Files::IStreamPtr&& stream, const ToUTF8::StatelessUtf8Encoder* encoder) : mReader(reader) , mStream(std::move(stream)) , mEncoder(encoder) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 6895a0238f..d60129cb86 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -207,8 +207,8 @@ namespace Resource namespace Resource { - KeyframeManager::KeyframeManager( - const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay, const ToUTF8::Utf8Encoder* encoder) + KeyframeManager::KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay, + const ToUTF8::StatelessUtf8Encoder* encoder) : ResourceManager(vfs, expiryDelay) , mSceneManager(sceneManager) , mEncoder(encoder) diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index f684e22ee7..ed8d4a04ab 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -11,7 +11,7 @@ namespace ToUTF8 { - class Utf8Encoder; + class StatelessUtf8Encoder; } namespace Resource @@ -54,7 +54,7 @@ namespace Resource { public: explicit KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay, - const ToUTF8::Utf8Encoder* encoder); + const ToUTF8::StatelessUtf8Encoder* encoder); ~KeyframeManager() = default; /// Retrieve a read-only keyframe resource by name (case-insensitive). @@ -65,7 +65,7 @@ namespace Resource private: SceneManager* mSceneManager; - const ToUTF8::Utf8Encoder* mEncoder; + const ToUTF8::StatelessUtf8Encoder* mEncoder; }; } diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index fb57bc8c85..352d367f9b 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -31,7 +31,7 @@ namespace Resource Nif::NIFFilePtr mNifFile; }; - NifFileManager::NifFileManager(const VFS::Manager* vfs, const ToUTF8::Utf8Encoder* encoder) + NifFileManager::NifFileManager(const VFS::Manager* vfs, const ToUTF8::StatelessUtf8Encoder* encoder) // NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager, // so no point in using an expiry delay. : ResourceManager(vfs, 0) diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index 2225a9fd4c..dab4b70748 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -7,7 +7,7 @@ namespace ToUTF8 { - class Utf8Encoder; + class StatelessUtf8Encoder; } namespace Resource @@ -17,10 +17,10 @@ namespace Resource /// @note May be used from any thread. class NifFileManager : public ResourceManager { - const ToUTF8::Utf8Encoder* mEncoder; + const ToUTF8::StatelessUtf8Encoder* mEncoder; public: - NifFileManager(const VFS::Manager* vfs, const ToUTF8::Utf8Encoder* encoder); + NifFileManager(const VFS::Manager* vfs, const ToUTF8::StatelessUtf8Encoder* encoder); ~NifFileManager(); /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 7d704e7d1e..65a83a60ab 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -10,7 +10,8 @@ namespace Resource { - ResourceSystem::ResourceSystem(const VFS::Manager* vfs, double expiryDelay, const ToUTF8::Utf8Encoder* encoder) + ResourceSystem::ResourceSystem( + const VFS::Manager* vfs, double expiryDelay, const ToUTF8::StatelessUtf8Encoder* encoder) : mVFS(vfs) { mNifFileManager = std::make_unique(vfs, encoder); diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 554083852e..f7f09b9277 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -17,7 +17,7 @@ namespace osg namespace ToUTF8 { - class Utf8Encoder; + class StatelessUtf8Encoder; } namespace Resource @@ -35,7 +35,8 @@ namespace Resource class ResourceSystem { public: - explicit ResourceSystem(const VFS::Manager* vfs, double expiryDelay, const ToUTF8::Utf8Encoder* encoder); + explicit ResourceSystem( + const VFS::Manager* vfs, double expiryDelay, const ToUTF8::StatelessUtf8Encoder* encoder); ~ResourceSystem(); SceneManager* getSceneManager(); diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 11a466e44c..80af6586c9 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -68,7 +68,7 @@ namespace ToUTF8 /// ASCII-only string. Otherwise returns a view to the input. std::string_view getLegacyEnc(std::string_view input); - StatelessUtf8Encoder getStatelessEncoder() const { return mImpl; } + const StatelessUtf8Encoder& getStatelessEncoder() const { return mImpl; } private: std::string mBuffer; From 35d9b18b4c009f6adf56421ee59dd7d8f34e69f9 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Jan 2024 22:26:56 +0100 Subject: [PATCH 27/47] Add type for normalized VFS path and use for VFS::Manager file map key This will reduce the number of path normalizations while more places will use this type. In some cases it also will reduce number of temporary allocations for new strings. For now make conversion from and to std::string_view implicit to allow gradual migration to this type. --- apps/openmw_test_suite/testing_util.hpp | 2 +- components/vfs/filemap.hpp | 7 +- components/vfs/manager.cpp | 2 +- components/vfs/pathutil.hpp | 68 +++++++++++++++++++ components/vfs/recursivedirectoryiterator.hpp | 5 +- 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index aa76f7f944..b819848a8f 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -61,7 +61,7 @@ namespace TestingOpenMW void listResources(VFS::FileMap& out) override { for (const auto& [key, value] : mFiles) - out.emplace(VFS::Path::normalizeFilename(key), value); + out.emplace(key, value); } bool contains(std::string_view file) const override { return mFiles.contains(file); } diff --git a/components/vfs/filemap.hpp b/components/vfs/filemap.hpp index 808153fc05..1b7d390d88 100644 --- a/components/vfs/filemap.hpp +++ b/components/vfs/filemap.hpp @@ -8,7 +8,12 @@ namespace VFS { class File; - using FileMap = std::map>; + namespace Path + { + class Normalized; + } + + using FileMap = std::map>; } #endif diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 5315f17252..d312ce9d84 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -83,7 +83,7 @@ namespace VFS return { mIndex.begin(), mIndex.end() }; std::string normalized = Path::normalizeFilename(path); const auto it = mIndex.lower_bound(normalized); - if (it == mIndex.end() || !it->first.starts_with(normalized)) + if (it == mIndex.end() || !it->first.view().starts_with(normalized)) return { it, it }; ++normalized.back(); return { it, mIndex.lower_bound(normalized) }; diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 724b406f1d..9bcc263842 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -51,6 +52,73 @@ namespace VFS::Path bool operator()(std::string_view left, std::string_view right) const { return pathLess(left, right); } }; + + class Normalized + { + public: + Normalized() = default; + + Normalized(std::string_view value) + : mValue(normalizeFilename(value)) + { + } + + Normalized(const char* value) + : Normalized(std::string_view(value)) + { + } + + Normalized(const std::string& value) + : Normalized(std::string_view(value)) + { + } + + explicit Normalized(std::string&& value) + : mValue(std::move(value)) + { + normalizeFilenameInPlace(mValue); + } + + const std::string& value() const& { return mValue; } + + std::string value() && { return std::move(mValue); } + + std::string_view view() const { return mValue; } + + operator std::string_view() const { return mValue; } + + operator const std::string&() const { return mValue; } + + friend bool operator==(const Normalized& lhs, const Normalized& rhs) = default; + + template + friend bool operator==(const Normalized& lhs, const T& rhs) + { + return lhs.mValue == rhs; + } + + friend bool operator<(const Normalized& lhs, const Normalized& rhs) { return lhs.mValue < rhs.mValue; } + + template + friend bool operator<(const Normalized& lhs, const T& rhs) + { + return lhs.mValue < rhs; + } + + template + friend bool operator<(const T& lhs, const Normalized& rhs) + { + return lhs < rhs.mValue; + } + + friend std::ostream& operator<<(std::ostream& stream, const Normalized& value) + { + return stream << value.mValue; + } + + private: + std::string mValue; + }; } #endif diff --git a/components/vfs/recursivedirectoryiterator.hpp b/components/vfs/recursivedirectoryiterator.hpp index 82f8e594fd..39fb26e873 100644 --- a/components/vfs/recursivedirectoryiterator.hpp +++ b/components/vfs/recursivedirectoryiterator.hpp @@ -4,6 +4,7 @@ #include #include "filemap.hpp" +#include "pathutil.hpp" namespace VFS { @@ -15,9 +16,9 @@ namespace VFS { } - const std::string& operator*() const { return mIt->first; } + const std::string& operator*() const { return mIt->first.value(); } - const std::string* operator->() const { return &mIt->first; } + const std::string* operator->() const { return &mIt->first.value(); } RecursiveDirectoryIterator& operator++() { From 3cdb9496c4db9b9bbd103ce06d1659e713d69606 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Thu, 18 Jan 2024 07:15:35 -0800 Subject: [PATCH 28/47] dont clear empty FBOs, fix doc example --- apps/openmw/mwrender/postprocessor.cpp | 5 +++++ docs/source/reference/postprocessing/omwfx.rst | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 1aaeb460b7..c82104ee4a 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -662,6 +662,11 @@ namespace MWRender for (const auto& name : pass->getRenderTargets()) { + if (name.empty()) + { + continue; + } + auto& renderTarget = technique->getRenderTargetsMap()[name]; subPass.mStateSet->setTextureAttribute(subTexUnit, renderTarget.mTarget); subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit)); diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index 7a7cdc198b..b47e509925 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -561,7 +561,7 @@ color buffer will accumulate. source_format = rgb; internal_format = rgb16f; source_type = float; - clear_color = vec4(1,0,0,1); + clear_color = vec4(0,0,0,1); } fragment red(target=RT_Red,blend=(add, src_color, one), rt1=RT_Red) { From fba405587731f799899d815a6381b7cb4f121869 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 18 Jan 2024 22:32:46 +0100 Subject: [PATCH 29/47] Move return comments to a new line --- apps/openmw/mwworld/cellstore.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index a8f296045a..0a78746479 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -209,8 +209,8 @@ namespace MWWorld /// false will abort the iteration. /// \note Prefer using forEachConst when possible. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in - /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration - /// completed? + /// unintended behaviour. \attention This function also lists deleted (count 0) objects! + /// \return Iteration completed? template bool forEach(Visitor&& visitor) { @@ -238,8 +238,8 @@ namespace MWWorld /// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in - /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration - /// completed? + /// unintended behaviour. \attention This function also lists deleted (count 0) objects! + /// \return Iteration completed? template bool forEachConst(Visitor&& visitor) const { @@ -263,8 +263,8 @@ namespace MWWorld /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning /// false will abort the iteration. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in - /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration - /// completed? + /// unintended behaviour. \attention This function also lists deleted (count 0) objects! + /// \return Iteration completed? template bool forEachType(Visitor&& visitor) { From ffa52dfe7c3f2a9fa9a05b14dc174c4ec4e387e1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 19 Jan 2024 11:55:37 +0300 Subject: [PATCH 30/47] Don't use height cull callback when there's no terrain --- components/terrain/world.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 93a9c563af..9c409b3bc2 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -145,7 +145,7 @@ namespace Terrain osg::Callback* World::getHeightCullCallback(float highz, unsigned int mask) { - if (!mHeightCullCallback) + if (!mHeightCullCallback || mTerrainRoot->getNumChildren() == 0) return nullptr; mHeightCullCallback->setHighZ(highz); From e997c44db6df01c62882fba5e166e6d9c5143bc5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 19 Jan 2024 12:53:35 +0300 Subject: [PATCH 31/47] Restore unwrapped Bullet triangle shape shallow copying --- components/resource/bulletshape.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 360b92ffc0..70348e956d 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -39,6 +39,13 @@ namespace Resource const_cast(trishape->getChildShape()), trishape->getLocalScaling())); } + if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) + { + const btBvhTriangleMeshShape* trishape = static_cast(shape); + return CollisionShapePtr(new btScaledBvhTriangleMeshShape( + const_cast(trishape), btVector3(1.f, 1.f, 1.f))); + } + if (shape->getShapeType() == BOX_SHAPE_PROXYTYPE) { const btBoxShape* boxshape = static_cast(shape); From b37aee21e3796a8d7f7633a80311e120fe87cf04 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Jan 2024 15:16:46 +0400 Subject: [PATCH 32/47] Fix tooltips in the main menu --- apps/openmw/engine.cpp | 6 +----- apps/openmw/mwworld/worldimp.cpp | 4 ++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 92483bd8c3..a059e7477f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -321,11 +321,7 @@ bool OMW::Engine::frame(float frametime) // update GUI by world data { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - - if (mStateManager->getState() != MWBase::StateManager::State_NoGame) - { - mWorld->updateWindowManager(); - } + mWorld->updateWindowManager(); } mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1b6af6038e..f20cbd208f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -60,6 +60,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -997,6 +998,9 @@ namespace MWWorld { MWWorld::Ptr facedObject; + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) + return facedObject; + if (MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getWindowManager()->isConsoleMode()) facedObject = getFacedObject(getMaxActivationDistance() * 50, false); From 87c9f395f11712470bcd6c134b79002d3d6031da Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Jan 2024 16:01:48 +0400 Subject: [PATCH 33/47] Move local variables in components --- components/bsa/ba2dx10file.cpp | 2 +- components/config/gamesettings.cpp | 2 +- components/detournavigator/asyncnavmeshupdater.cpp | 2 +- components/esm3/inventorystate.cpp | 2 +- components/esm3/spellstate.cpp | 4 ++-- components/esm4/loadfurn.cpp | 2 +- components/esmterrain/storage.cpp | 6 +++--- components/files/configurationmanager.cpp | 2 +- components/lua/asyncpackage.cpp | 2 +- components/lua_ui/element.cpp | 2 +- components/lua_ui/scriptsettings.cpp | 2 +- components/nif/niffile.cpp | 2 +- components/settings/parser.cpp | 2 +- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index 945e8dd931..593ca64949 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -76,7 +76,7 @@ namespace Bsa fail("Corrupted BSA"); } - mFolders[dirHash][{ nameHash, extHash }] = file; + mFolders[dirHash][{ nameHash, extHash }] = std::move(file); FileStruct fileStruct{}; mFiles.push_back(fileStruct); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 42c11628df..ad7c73d3d9 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -371,7 +371,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) { if ((keyMatch.captured(1) + "=" + keyMatch.captured(2)) == keyVal) { - *iter = settingLine; + *iter = std::move(settingLine); break; } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 980281240d..ec6313d6f1 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -601,7 +601,7 @@ namespace DetourNavigator if (mSettings.get().mEnableRecastMeshFileNameRevision) recastMeshRevision = revision; if (mSettings.get().mEnableNavMeshFileNameRevision) - navMeshRevision = revision; + navMeshRevision = std::move(revision); } if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) writeToFile(*recastMesh, diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index 1947be23e9..f3dce52f29 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -74,7 +74,7 @@ namespace ESM esm.getHNT(multiplier, "MULT"); params.emplace_back(rand, multiplier); } - mPermanentMagicEffectMagnitudes[id] = params; + mPermanentMagicEffectMagnitudes[id] = std::move(params); } while (esm.isNextSub("EQUI")) diff --git a/components/esm3/spellstate.cpp b/components/esm3/spellstate.cpp index 41591f56b7..39c98e7c0f 100644 --- a/components/esm3/spellstate.cpp +++ b/components/esm3/spellstate.cpp @@ -33,7 +33,7 @@ namespace ESM state.mPurgedEffects.insert(index); } - mSpellParams[id] = state; + mSpellParams[id] = std::move(state); mSpells.emplace_back(id); } } @@ -69,7 +69,7 @@ namespace ESM esm.getHNT(info.mMagnitude, "MAGN"); permEffectList.push_back(info); } - mPermanentSpellEffects[spellId] = permEffectList; + mPermanentSpellEffects[spellId] = std::move(permEffectList); } // Obsolete diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp index 40ea04955e..41ddca07a2 100644 --- a/components/esm4/loadfurn.cpp +++ b/components/esm4/loadfurn.cpp @@ -50,7 +50,7 @@ void ESM4::Furniture::load(ESM4::Reader& reader) reader.getLocalizedString(name); // FIXME: subsequent FULL subrecords name object combinations (FO4) if (mFullName.empty()) - mFullName = name; + mFullName = std::move(name); break; } case ESM4::SUB_MODL: diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index d8cf964f71..a00cca0904 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -586,7 +586,7 @@ namespace ESMTerrain Misc::StringUtils::replaceLast(texture_, ".", mNormalHeightMapPattern + "."); if (mVFS->exists(texture_)) { - info.mNormalMap = texture_; + info.mNormalMap = std::move(texture_); info.mParallax = true; } else @@ -594,7 +594,7 @@ namespace ESMTerrain texture_ = texture; Misc::StringUtils::replaceLast(texture_, ".", mNormalMapPattern + "."); if (mVFS->exists(texture_)) - info.mNormalMap = texture_; + info.mNormalMap = std::move(texture_); } } @@ -604,7 +604,7 @@ namespace ESMTerrain Misc::StringUtils::replaceLast(texture_, ".", mSpecularMapPattern + "."); if (mVFS->exists(texture_)) { - info.mDiffuseMap = texture_; + info.mDiffuseMap = std::move(texture_); info.mSpecular = true; } } diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index ece30e5b3f..943f514676 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -309,7 +309,7 @@ namespace Files tempPath /= str.substr(pos + 1, str.length() - pos); } - path = tempPath; + path = std::move(tempPath); } else { diff --git a/components/lua/asyncpackage.cpp b/components/lua/asyncpackage.cpp index b60238de13..8316ab2cde 100644 --- a/components/lua/asyncpackage.cpp +++ b/components/lua/asyncpackage.cpp @@ -85,7 +85,7 @@ namespace LuaUtil auto initializer = [](sol::table hiddenData) { ScriptId id = hiddenData[ScriptsContainer::sScriptIdKey]; - return AsyncPackageId{ id.mContainer, id.mIndex, hiddenData }; + return AsyncPackageId{ id.mContainer, id.mIndex, std::move(hiddenData) }; }; return sol::make_object(lua, initializer); } diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 84383f89e1..e993fba9fd 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -38,7 +38,7 @@ namespace LuaUi if (typeField != sol::nil && templateType != type) throw std::logic_error(std::string("Template layout type ") + type + std::string(" doesn't match template type ") + templateType); - type = templateType; + type = std::move(templateType); } return type; } diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp index e92d1d8958..d552b7b3d6 100644 --- a/components/lua_ui/scriptsettings.cpp +++ b/components/lua_ui/scriptsettings.cpp @@ -21,7 +21,7 @@ namespace LuaUi Log(Debug::Warning) << "A script settings page has an empty name"; if (!element.get()) Log(Debug::Warning) << "A script settings page has no UI element assigned"; - return { std::move(name), std::move(searchHints), element }; + return { std::move(name), std::move(searchHints), std::move(element) }; } } diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index d6d063a254..7b20637c70 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -672,7 +672,7 @@ namespace Nif assert(r != nullptr); assert(r->recType != RC_MISSING); - r->recName = rec; + r->recName = std::move(rec); r->recIndex = i; r->read(&nif); mRecords[i] = std::move(r); diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index 5ec41c5f4b..ff6bc5ca48 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -77,7 +77,7 @@ void Settings::SettingsFileParser::loadSettingsFile( Misc::StringUtils::trim(value); if (overrideExisting) - settings[std::make_pair(currentCategory, setting)] = value; + settings[std::make_pair(currentCategory, setting)] = std::move(value); else if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) fail(std::string("duplicate setting: [" + currentCategory + "] " + setting)); } From 8af8f331cb67d7a49c44a494aa2bb07d710d3f2b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Jan 2024 18:19:01 +0400 Subject: [PATCH 34/47] Avoid possible race in videoplayer --- extern/osg-ffmpeg-videoplayer/videostate.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 096651dfd8..c062c99b65 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -598,8 +598,17 @@ public: if(av_read_frame(pFormatCtx, packet.get()) < 0) { - if (self->audioq.nb_packets == 0 && self->videoq.nb_packets == 0 && self->pictq_size == 0) - self->mVideoEnded = true; + if (self->audioq.nb_packets == 0 && self->videoq.nb_packets == 0) + { + self->pictq_mutex.lock(); + bool videoEnded = self->pictq_size == 0; + self->pictq_mutex.unlock(); + if (videoEnded) + self->mVideoEnded = true; + else + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + continue; } else From 1f416d7c8ae0ec882c99b554619b4e33d1a40989 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 20 Jan 2024 11:56:36 +0000 Subject: [PATCH 35/47] Lua: Creature skill bindings --- apps/openmw/mwlua/types/creature.cpp | 15 +++++++++++++++ files/lua_api/openmw/types.lua | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index ddf90bf8c5..dd4b1bd67b 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -1,3 +1,4 @@ +#include "../stats.hpp" #include "actor.hpp" #include "types.hpp" @@ -42,6 +43,20 @@ namespace MWLua record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; }); record["type"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mType; }); record["baseGold"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mGold; }); + record["combatSkill"] + = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mCombat; }); + record["magicSkill"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mMagic; }); + record["stealthSkill"] + = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mStealth; }); + record["attack"] = sol::readonly_property([context](const ESM::Creature& rec) -> sol::table { + sol::state_view& lua = context.mLua->sol(); + sol::table res(lua, sol::create); + int index = 1; + for (auto attack : rec.mData.mAttack) + res[index++] = attack; + return LuaUtil::makeReadOnly(res); + }); + addActorServicesBindings(record, context); } } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index ad30994fe8..ba0b2dd58b 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -717,6 +717,11 @@ -- @param #any objectOrRecordId -- @return #CreatureRecord +--- +-- @type CreatureAttack +-- @field #number minDamage Minimum attack damage. +-- @field #number maxDamage Maximum attack damage. + --- -- @type CreatureRecord -- @field #string id The record ID of the creature @@ -727,6 +732,10 @@ -- @field #number soulValue The soul value of the creature record -- @field #number type The @{#Creature.TYPE} of the creature -- @field #number baseGold The base barter gold of the creature +-- @field #number combatSkill The base combat skill of the creature. This is the skill value used for all skills with a 'combat' specialization +-- @field #number magicSkill The base magic skill of the creature. This is the skill value used for all skills with a 'magic' specialization +-- @field #number stealthSkill The base stealth skill of the creature. This is the skill value used for all skills with a 'stealth' specialization +-- @field #list<#number> attack A table of the 3 randomly selected attacks used by creatures that do not carry weapons. The table consists of 6 numbers split into groups of 2 values corresponding to minimum and maximum damage in that order. -- @field #map<#string, #boolean> servicesOffered The services of the creature, in a table. Value is if the service is provided or not, and they are indexed by: Spells, Spellmaking, Enchanting, Training, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. From 251d01304fc1aa7bd0f199a9636214bd8afcc00a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 21 Jan 2024 13:48:33 +0400 Subject: [PATCH 36/47] Use move semantics for osg::ref_ptr --- apps/opencs/view/render/worldspacewidget.cpp | 2 +- apps/openmw/engine.cpp | 2 +- apps/openmw/mwrender/actoranimation.cpp | 2 +- apps/openmw/mwrender/globalmap.cpp | 7 ++++--- apps/openmw/mwrender/luminancecalculator.cpp | 2 +- apps/openmw/mwrender/objects.cpp | 2 +- apps/openmw/mwrender/ripples.cpp | 2 +- apps/openmw/mwrender/sky.cpp | 4 ++-- apps/openmw/mwrender/water.cpp | 4 ++-- components/nifosg/nifloader.cpp | 2 +- components/resource/keyframemanager.cpp | 4 ++-- components/sceneutil/util.cpp | 2 +- components/shader/shadervisitor.cpp | 10 +++++----- 13 files changed, 23 insertions(+), 22 deletions(-) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index f7732d752d..2af84fb36d 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -440,7 +440,7 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick( osg::Node* node = *nodeIter; if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) { - WorldspaceHitResult hit = { true, tag, 0, 0, 0, intersection.getWorldIntersectPoint() }; + WorldspaceHitResult hit = { true, std::move(tag), 0, 0, 0, intersection.getWorldIntersectPoint() }; if (intersection.indexList.size() >= 3) { hit.index0 = intersection.indexList[0]; diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3f3d5fe558..dfc11c309b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -823,7 +823,7 @@ void OMW::Engine::prepareEngine() } listener->loadingOff(); - mWorld->init(mViewer, rootNode, mWorkQueue.get(), *mUnrefQueue); + mWorld->init(mViewer, std::move(rootNode), mWorkQueue.get(), *mUnrefQueue); mEnvironment.setWorldScene(mWorld->getWorldScene()); mWorld->setupPlayer(); mWorld->setRandomSeed(mRandomSeed); diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index e31a1eb711..2c70cd0436 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -101,7 +101,7 @@ namespace MWRender templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager(), &rotation); } return SceneUtil::attach( - templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager()); + std::move(templateNode), mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager()); } std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index ac7a8a9351..e58f987a44 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -422,7 +422,8 @@ namespace MWRender if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY) return; - requestOverlayTextureUpdate(originX, mHeight - originY, cellSize, cellSize, localMapTexture, false, true); + requestOverlayTextureUpdate( + originX, mHeight - originY, cellSize, cellSize, std::move(localMapTexture), false, true); } void GlobalMap::clear() @@ -554,7 +555,7 @@ namespace MWRender { mOverlayImage = image; - requestOverlayTextureUpdate(0, 0, mWidth, mHeight, texture, true, false); + requestOverlayTextureUpdate(0, 0, mWidth, mHeight, std::move(texture), true, false); } else { @@ -562,7 +563,7 @@ namespace MWRender // In the latter case, we'll want filtering. // Create a RTT Camera and draw the image onto mOverlayImage in the next frame. requestOverlayTextureUpdate(destBox.mLeft, destBox.mTop, destBox.mRight - destBox.mLeft, - destBox.mBottom - destBox.mTop, texture, true, true, srcBox.mLeft / float(imageWidth), + destBox.mBottom - destBox.mTop, std::move(texture), true, true, srcBox.mLeft / float(imageWidth), srcBox.mTop / float(imageHeight), srcBox.mRight / float(imageWidth), srcBox.mBottom / float(imageHeight)); } diff --git a/apps/openmw/mwrender/luminancecalculator.cpp b/apps/openmw/mwrender/luminancecalculator.cpp index ae29b7fdcc..30918db87c 100644 --- a/apps/openmw/mwrender/luminancecalculator.cpp +++ b/apps/openmw/mwrender/luminancecalculator.cpp @@ -19,7 +19,7 @@ namespace MWRender auto resolveFragment = shaderManager.getShader("luminance/resolve.frag", defines); mResolveProgram = shaderManager.getProgram(vertex, std::move(resolveFragment)); - mLuminanceProgram = shaderManager.getProgram(vertex, std::move(luminanceFragment)); + mLuminanceProgram = shaderManager.getProgram(std::move(vertex), std::move(luminanceFragment)); for (auto& buffer : mBuffers) { diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 89e192f6c8..d93dc47641 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -68,7 +68,7 @@ namespace MWRender ptr.getClass().adjustScale(ptr, scaleVec, true); insert->setScale(scaleVec); - ptr.getRefData().setBaseNode(insert); + ptr.getRefData().setBaseNode(std::move(insert)); } void Objects::insertModel(const MWWorld::Ptr& ptr, const std::string& mesh, bool allowLight) diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index 130e005729..dea372666e 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -106,7 +106,7 @@ namespace MWRender mProgramBlobber = shaderManager.getProgram( vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT)); mProgramSimulation = shaderManager.getProgram( - vertex, shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT)); + std::move(vertex), shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT)); } void RipplesSurface::setupComputePipeline() diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 6df3734252..060b6ee5de 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -764,7 +764,7 @@ namespace MWRender cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mCloudUpdater->setTexture(cloudTex); + mCloudUpdater->setTexture(std::move(cloudTex)); } if (mStormDirection != weather.mStormDirection) @@ -786,7 +786,7 @@ namespace MWRender cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mNextCloudUpdater->setTexture(cloudTex); + mNextCloudUpdater->setTexture(std::move(cloudTex)); mNextStormDirection = weather.mStormDirection; } } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 553bdeeaaa..d5fb01242f 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -722,8 +722,8 @@ namespace MWRender mRainSettingsUpdater = new RainSettingsUpdater(); node->setUpdateCallback(mRainSettingsUpdater); - mShaderWaterStateSetUpdater - = new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, mRipples, std::move(program), normalMap); + mShaderWaterStateSetUpdater = new ShaderWaterStateSetUpdater( + this, mReflection, mRefraction, mRipples, std::move(program), std::move(normalMap)); node->addCullCallback(mShaderWaterStateSetUpdater); } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 2f7574d68b..8d46b0f751 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1660,7 +1660,7 @@ namespace NifOsg && bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Skinned) { osg::ref_ptr rig(new SceneUtil::RigGeometry); - rig->setSourceGeometry(geometry); + rig->setSourceGeometry(std::move(geometry)); const Nif::BSSkinInstance* skin = static_cast(bsTriShape->mSkin.getPtr()); const Nif::BSSkinBoneData* data = skin->mData.getPtr(); diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index d60129cb86..68b7adbe9a 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -123,7 +123,7 @@ namespace Resource mergedAnimationTrack->addChannel(channel.get()->clone()); } - callback->addMergedAnimationTrack(mergedAnimationTrack); + callback->addMergedAnimationTrack(std::move(mergedAnimationTrack)); float startTime = animation->getStartTime(); float stopTime = startTime + animation->getDuration(); @@ -239,7 +239,7 @@ namespace Resource = dynamic_cast(scene->getUpdateCallback()); if (bam) { - Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam, normalized, mVFS); + Resource::RetrieveAnimationsVisitor rav(*loaded.get(), std::move(bam), normalized, mVFS); scene->accept(rav); } } diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index ce48702a74..ab600de11d 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -248,7 +248,7 @@ namespace SceneUtil } writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); - resourceSystem->getSceneManager()->recreateShaders(node); + resourceSystem->getSceneManager()->recreateShaders(std::move(node)); return glowUpdater; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 70464f571e..e281f64448 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -740,7 +740,7 @@ namespace Shader auto program = mShaderManager.getProgram(shaderPrefix, defineMap, mProgramTemplate); writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); - addedState->setAttributeAndModes(program); + addedState->setAttributeAndModes(std::move(program)); for (const auto& [unit, name] : reqs.mTextures) { @@ -934,13 +934,13 @@ namespace Shader { osg::ref_ptr sourceGeometry = rig->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) - rig->setSourceGeometry(sourceGeometry); + rig->setSourceGeometry(std::move(sourceGeometry)); } else if (auto morph = dynamic_cast(&drawable)) { osg::ref_ptr sourceGeometry = morph->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) - morph->setSourceGeometry(sourceGeometry); + morph->setSourceGeometry(std::move(sourceGeometry)); } else if (auto osgaRig = dynamic_cast(&drawable)) { @@ -948,8 +948,8 @@ namespace Shader osg::ref_ptr sourceGeometry = sourceOsgaRigGeometry->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) { - sourceOsgaRigGeometry->setSourceGeometry(sourceGeometry); - osgaRig->setSourceRigGeometry(sourceOsgaRigGeometry); + sourceOsgaRigGeometry->setSourceGeometry(std::move(sourceGeometry)); + osgaRig->setSourceRigGeometry(std::move(sourceOsgaRigGeometry)); } } From 737d3b499b90290e69365d0e8306a657ef64e4f7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 21 Jan 2024 20:20:37 +0400 Subject: [PATCH 37/47] Use move semantics for tools --- apps/essimporter/convertacdt.cpp | 2 +- apps/essimporter/converter.cpp | 6 +++--- apps/opencs/model/filter/parser.cpp | 2 +- apps/opencs/model/tools/reportmodel.cpp | 2 +- apps/opencs/model/world/actoradapter.cpp | 8 ++++---- apps/opencs/model/world/collection.hpp | 2 +- apps/opencs/view/world/tablesubview.cpp | 2 +- apps/wizard/unshield/unshieldworker.cpp | 7 +++---- 8 files changed, 15 insertions(+), 16 deletions(-) diff --git a/apps/essimporter/convertacdt.cpp b/apps/essimporter/convertacdt.cpp index 8342310cf6..a737e0a3a2 100644 --- a/apps/essimporter/convertacdt.cpp +++ b/apps/essimporter/convertacdt.cpp @@ -85,7 +85,7 @@ namespace ESSImport Misc::StringUtils::lowerCaseInPlace(group); ESM::AnimationState::ScriptedAnimation scriptedAnim; - scriptedAnim.mGroup = group; + scriptedAnim.mGroup = std::move(group); scriptedAnim.mTime = anis.mTime; scriptedAnim.mAbsolute = true; // Neither loop count nor queueing seems to be supported by the ess format. diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 07146fc388..4c4bd1e438 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -306,12 +306,12 @@ namespace ESSImport mMarkers.push_back(marker); } - newcell.mRefs = cellrefs; + newcell.mRefs = std::move(cellrefs); if (cell.isExterior()) - mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; + mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = std::move(newcell); else - mIntCells[cell.mName] = newcell; + mIntCells[cell.mName] = std::move(newcell); } void ConvertCell::writeCell(const Cell& cell, ESM::ESMWriter& esm) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 5443db2854..aadad5f8f5 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -624,7 +624,7 @@ bool CSMFilter::Parser::parse(const std::string& filter, bool allowPredefined) } if (node) - mFilter = node; + mFilter = std::move(node); else { // Empty filter string equals to filter "true". diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 84a8c71f95..f9251acdab 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -171,7 +171,7 @@ void CSMTools::ReportModel::flagAsReplaced(int index) hint[0] = 'r'; - line.mHint = hint; + line.mHint = std::move(hint); emit dataChanged(this->index(index, 0), this->index(index, columnCount())); } diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 0e3725bbb7..37aaf08445 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -468,13 +468,13 @@ namespace CSMWorld if (type == UniversalId::Type_Creature) { // Valid creature record - setupCreature(id, data); + setupCreature(id, std::move(data)); emit actorChanged(id); } else if (type == UniversalId::Type_Npc) { // Valid npc record - setupNpc(id, data); + setupNpc(id, std::move(data)); emit actorChanged(id); } else @@ -665,7 +665,7 @@ namespace CSMWorld RaceDataPtr data = mCachedRaces.get(race); if (data) { - setupRace(race, data); + setupRace(race, std::move(data)); // Race was changed. Need to mark actor dependencies as dirty. // Cannot use markDirtyDependency because that would invalidate // the current iterator. @@ -683,7 +683,7 @@ namespace CSMWorld ActorDataPtr data = mCachedActors.get(actor); if (data) { - setupActor(actor, data); + setupActor(actor, std::move(data)); } } mDirtyActors.clear(); diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 9db6b3b042..dbbff2ed4a 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -504,7 +504,7 @@ namespace CSMWorld auto record2 = std::make_unique>(); record2->mState = Record::State_ModifiedOnly; - record2->mModified = record; + record2->mModified = std::move(record); insertRecord(std::move(record2), getAppendIndex(id, type), type); } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 1d4dc37529..891d954ad4 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -195,7 +195,7 @@ void CSVWorld::TableSubView::createFilterRequest(std::vector Date: Sun, 21 Jan 2024 12:54:33 +0400 Subject: [PATCH 38/47] Fix error message about savegame format --- apps/openmw/mwstate/statemanagerimp.cpp | 99 +++++++++++++++++-------- apps/openmw/mwstate/statemanagerimp.hpp | 2 + 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 631ef9a112..8d3b84df13 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -409,9 +409,38 @@ void MWState::StateManager::loadGame(const std::filesystem::path& filepath) loadGame(character, filepath); } -struct VersionMismatchError : public std::runtime_error +struct SaveFormatVersionError : public std::exception { - using std::runtime_error::runtime_error; + using std::exception::exception; + + SaveFormatVersionError(ESM::FormatVersion savegameFormat, const std::string& message) + : mSavegameFormat(savegameFormat) + , mErrorMessage(message) + { + } + + const char* what() const noexcept override { return mErrorMessage.c_str(); } + ESM::FormatVersion getFormatVersion() const { return mSavegameFormat; } + +protected: + ESM::FormatVersion mSavegameFormat = ESM::DefaultFormatVersion; + std::string mErrorMessage; +}; + +struct SaveVersionTooOldError : SaveFormatVersionError +{ + SaveVersionTooOldError(ESM::FormatVersion savegameFormat) + : SaveFormatVersionError(savegameFormat, "format version " + std::to_string(savegameFormat) + " is too old") + { + } +}; + +struct SaveVersionTooNewError : SaveFormatVersionError +{ + SaveVersionTooNewError(ESM::FormatVersion savegameFormat) + : SaveFormatVersionError(savegameFormat, "format version " + std::to_string(savegameFormat) + " is too new") + { + } }; void MWState::StateManager::loadGame(const Character* character, const std::filesystem::path& filepath) @@ -427,23 +456,9 @@ void MWState::StateManager::loadGame(const Character* character, const std::file ESM::FormatVersion version = reader.getFormatVersion(); if (version > ESM::CurrentSaveGameFormatVersion) - throw VersionMismatchError("#{OMWEngine:LoadingRequiresNewVersionError}"); + throw SaveVersionTooNewError(version); else if (version < ESM::MinSupportedSaveGameFormatVersion) - { - const char* release; - // Report the last version still capable of reading this save - if (version <= ESM::OpenMW0_48SaveGameFormatVersion) - release = "OpenMW 0.48.0"; - else - { - // Insert additional else if statements above to cover future releases - static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49SaveGameFormatVersion); - release = "OpenMW 0.49.0"; - } - auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine"); - std::string message = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release }); - throw VersionMismatchError(message); - } + throw SaveVersionTooOldError(version); std::map contentFileMap = buildContentFileIndexMap(reader); reader.setContentFileMapping(&contentFileMap); @@ -625,23 +640,49 @@ void MWState::StateManager::loadGame(const Character* character, const std::file MWBase::Environment::get().getLuaManager()->gameLoaded(); } + catch (const SaveVersionTooNewError& e) + { + std::string error = "#{OMWEngine:LoadingRequiresNewVersionError}"; + printSavegameFormatError(e.what(), error); + } + catch (const SaveVersionTooOldError& e) + { + const char* release; + // Report the last version still capable of reading this save + if (e.getFormatVersion() <= ESM::OpenMW0_48SaveGameFormatVersion) + release = "OpenMW 0.48.0"; + else + { + // Insert additional else if statements above to cover future releases + static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49SaveGameFormatVersion); + release = "OpenMW 0.49.0"; + } + auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine"); + std::string error = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release }); + printSavegameFormatError(e.what(), error); + } catch (const std::exception& e) { - Log(Debug::Error) << "Failed to load saved game: " << e.what(); - - cleanup(true); - - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); - - std::vector buttons; - buttons.emplace_back("#{Interface:OK}"); - std::string error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what()); - - MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error, buttons); + printSavegameFormatError(e.what(), error); } } +void MWState::StateManager::printSavegameFormatError( + const std::string& exceptionText, const std::string& messageBoxText) +{ + Log(Debug::Error) << "Failed to load saved game: " << exceptionText; + + cleanup(true); + + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); + + std::vector buttons; + buttons.emplace_back("#{Interface:OK}"); + + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(messageBoxText, buttons); +} + void MWState::StateManager::quickLoad() { if (Character* currentCharacter = getCurrentCharacter()) diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index a76b829e38..c25e43cc23 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -22,6 +22,8 @@ namespace MWState private: void cleanup(bool force = false); + void printSavegameFormatError(const std::string& exceptionText, const std::string& messageBoxText); + bool confirmLoading(const std::vector& missingFiles) const; void writeScreenshot(std::vector& imageData) const; From 84ab7afd444f37e122e250ac4ca2e922472c42f2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 21 Jan 2024 22:54:54 +0300 Subject: [PATCH 39/47] Make BA2 extension hash calculation safer (#7784) --- components/bsa/ba2dx10file.cpp | 2 +- components/bsa/ba2file.cpp | 8 ++++++++ components/bsa/ba2file.hpp | 1 + components/bsa/ba2gnrlfile.cpp | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index 593ca64949..aa3f8d0581 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -177,7 +177,7 @@ namespace Bsa return std::nullopt; // folder not found uint32_t fileHash = generateHash(fileName); - uint32_t extHash = *reinterpret_cast(ext.data() + 1); + uint32_t extHash = generateExtensionHash(ext); auto iter = it->second.find({ fileHash, extHash }); if (iter == it->second.end()) return std::nullopt; // file not found diff --git a/components/bsa/ba2file.cpp b/components/bsa/ba2file.cpp index b4fc7f9ec2..17cfb03866 100644 --- a/components/bsa/ba2file.cpp +++ b/components/bsa/ba2file.cpp @@ -46,4 +46,12 @@ namespace Bsa return result; } + uint32_t generateExtensionHash(std::string_view extension) + { + uint32_t result = 0; + for (size_t i = 0; i < 4 && i < extension.size() - 1; i++) + result |= static_cast(extension[i + 1]) << (8 * i); + return result; + } + } // namespace Bsa diff --git a/components/bsa/ba2file.hpp b/components/bsa/ba2file.hpp index e5a68d3caa..75a2ce8d61 100644 --- a/components/bsa/ba2file.hpp +++ b/components/bsa/ba2file.hpp @@ -7,6 +7,7 @@ namespace Bsa { uint32_t generateHash(const std::string& name); + uint32_t generateExtensionHash(std::string_view extension); } #endif diff --git a/components/bsa/ba2gnrlfile.cpp b/components/bsa/ba2gnrlfile.cpp index f3961a3bc4..02df12593c 100644 --- a/components/bsa/ba2gnrlfile.cpp +++ b/components/bsa/ba2gnrlfile.cpp @@ -172,7 +172,7 @@ namespace Bsa return FileRecord(); // folder not found, return default which has offset of sInvalidOffset uint32_t fileHash = generateHash(fileName); - uint32_t extHash = *reinterpret_cast(ext.data() + 1); + uint32_t extHash = generateExtensionHash(ext); auto iter = it->second.find({ fileHash, extHash }); if (iter == it->second.end()) return FileRecord(); // file not found, return default which has offset of sInvalidOffset From 0ea88df46dd7ceb7b7fb4477fd68db159792340c Mon Sep 17 00:00:00 2001 From: Diego Date: Sun, 21 Jan 2024 19:48:13 -0500 Subject: [PATCH 40/47] added instructions for extracting .esm files using innoextract --- .../installation/install-game-files.rst | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 57460c4983..1925d16d97 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -68,6 +68,51 @@ You will find ``Morrowind.esm`` there. Users of other platforms running Wine, will find it at ``~/.wine/drive_c/Program Files/Bethesda Softworks/Morrowind`` +Innoextract +^^^^^^^^^^^ + +Linux +~~~~~ + +If you have purchased "The Elder Scrolls III: Morrowind" from GOG and wish to extract the game files on a Linux system without using Wine, you can do so using ``innoextract``. + +For Distributions Using `apt` (e.g., Ubuntu, Debian) +++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. code:: bash + + sudo apt update + sudo apt install innoextract + +Other Distributions ++++++++++++++++++++ + +If you are using a Linux distribution that doesn't use apt, search your package manager for the program and install it if it exists. + +.. code:: bash + + # openSUSE + sudo zypper refresh + sudo zypper search innoextract + sudo zypper install innoextract + +.. code:: bash + + # Arch Linux/Manjaro + sudo pacman -Sy + sudo pacman -Ss innoextract + sudo pacman -S innoextract + +Once the program is installed, download the game from GOG. The file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When you run ``innoextract`` it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. + +.. code:: bash + + innoextract setup_tes_morrowind_goty_2.0.0.7.exe -d ~/Documents/Games/Morrowind/ + +If not just run the command without the ``-d`` flag. Assuming you used the filepath above, your ``.esm`` files will be located in this diredctory ``~/Documents/Games/Morrowind/app/Data Files/``. + +Now you can run the OpenMW launcher and run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and enjoy playing Morrowind. + ----- Steam ----- From 0f3c4f2043ceca1a2585e1427de4e228a081a591 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 22 Jan 2024 12:41:53 +0000 Subject: [PATCH 41/47] Applying Feedback --- docs/source/manuals/installation/install-game-files.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 1925d16d97..57f8d65a88 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -74,7 +74,7 @@ Innoextract Linux ~~~~~ -If you have purchased "The Elder Scrolls III: Morrowind" from GOG and wish to extract the game files on a Linux system without using Wine, you can do so using ``innoextract``. +If you have purchased "The Elder Scrolls III: Morrowind" `from GOG `_ and wish to extract the game files on a Linux system without using Wine, you can do so using `innoextract `_. For Distributions Using `apt` (e.g., Ubuntu, Debian) ++++++++++++++++++++++++++++++++++++++++++++++++++++ From 6a1979e5f1ded54e9b40cde58071f9cca9613655 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 22 Jan 2024 12:42:42 +0000 Subject: [PATCH 42/47] Applying feedback to fix the verbiage of using innoextract --- docs/source/manuals/installation/install-game-files.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 57f8d65a88..21f01983f3 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -103,7 +103,7 @@ If you are using a Linux distribution that doesn't use apt, search your package sudo pacman -Ss innoextract sudo pacman -S innoextract -Once the program is installed, download the game from GOG. The file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When you run ``innoextract`` it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. +Once the innoextract is installed, download the game from GOG. The downloaded file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When ``innoextract`` is run on it, it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. .. code:: bash From d6d1cb099f3e4908bcf088f7e5cc312bc81dc430 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 22 Jan 2024 12:43:20 +0000 Subject: [PATCH 43/47] applying feedback to switch from bash to console --- docs/source/manuals/installation/install-game-files.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 21f01983f3..c8464c4f57 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -105,9 +105,9 @@ If you are using a Linux distribution that doesn't use apt, search your package Once the innoextract is installed, download the game from GOG. The downloaded file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When ``innoextract`` is run on it, it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. -.. code:: bash +.. code:: console - innoextract setup_tes_morrowind_goty_2.0.0.7.exe -d ~/Documents/Games/Morrowind/ + $ innoextract ./setup_tes_morrowind_goty_2.0.0.7.exe -d ~/Documents/Games/Morrowind/ If not just run the command without the ``-d`` flag. Assuming you used the filepath above, your ``.esm`` files will be located in this diredctory ``~/Documents/Games/Morrowind/app/Data Files/``. From a7473a2134e1fb43e641bcd93e3f4268b2c8b20c Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 22 Jan 2024 12:44:07 +0000 Subject: [PATCH 44/47] Applying feedback to remove redundant instructions --- docs/source/manuals/installation/install-game-files.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index c8464c4f57..5d5350bde2 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -109,7 +109,7 @@ Once the innoextract is installed, download the game from GOG. The downloaded fi $ innoextract ./setup_tes_morrowind_goty_2.0.0.7.exe -d ~/Documents/Games/Morrowind/ -If not just run the command without the ``-d`` flag. Assuming you used the filepath above, your ``.esm`` files will be located in this diredctory ``~/Documents/Games/Morrowind/app/Data Files/``. +Assuming you used the filepath above, your ``.esm`` files will be located in ``~/Documents/Games/Morrowind/app/Data Files/``. Now you can run the OpenMW launcher and run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and enjoy playing Morrowind. From 6029545bc7ff9806eedda2f5bf470f50051fe7ef Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 22 Jan 2024 12:45:01 +0000 Subject: [PATCH 45/47] Applying feedback to make verbiage less casual --- docs/source/manuals/installation/install-game-files.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 5d5350bde2..4980f902a7 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -111,7 +111,7 @@ Once the innoextract is installed, download the game from GOG. The downloaded fi Assuming you used the filepath above, your ``.esm`` files will be located in ``~/Documents/Games/Morrowind/app/Data Files/``. -Now you can run the OpenMW launcher and run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and enjoy playing Morrowind. +You can now run the OpenMW launcher and from there run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and follow the instructions. ----- Steam From 5c135551e611e274a798855c696f92551c8ead14 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 22 Jan 2024 07:49:25 -0500 Subject: [PATCH 46/47] removed zypper and pacman commands --- .../installation/install-game-files.rst | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 4980f902a7..413061f8c4 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -79,31 +79,12 @@ If you have purchased "The Elder Scrolls III: Morrowind" `from GOG Date: Mon, 22 Jan 2024 14:33:52 +0000 Subject: [PATCH 47/47] added instructions to install innoextract using homebrew --- .../manuals/installation/install-game-files.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 413061f8c4..6da5d3d55a 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -71,10 +71,10 @@ Users of other platforms running Wine, will find it at Innoextract ^^^^^^^^^^^ -Linux -~~~~~ +macOS and Linux +~~~~~~~~~~~~~~~ -If you have purchased "The Elder Scrolls III: Morrowind" `from GOG `_ and wish to extract the game files on a Linux system without using Wine, you can do so using `innoextract `_. +If you have purchased "The Elder Scrolls III: Morrowind" `from GOG `_ and wish to extract the game files on a Linux system without using Wine, or on macOS, you can do so using `innoextract `_. First install innoextract. For Distributions Using `apt` (e.g., Ubuntu, Debian) ++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -84,6 +84,13 @@ For Distributions Using `apt` (e.g., Ubuntu, Debian) sudo apt update sudo apt install innoextract +For macOS using Homebrew +++++++++++++++++++++++++ + +.. code:: console + + brew install innoextract + Once innoextract is installed, download the game from GOG. The downloaded file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When ``innoextract`` is run on it, it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. .. code:: console @@ -92,7 +99,7 @@ Once innoextract is installed, download the game from GOG. The downloaded file s Assuming you used the filepath above, your ``.esm`` files will be located in ``~/Documents/Games/Morrowind/app/Data Files/``. -You can now run the OpenMW launcher and from there run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and follow the instructions. +You can now run the OpenMW launcher, and from there run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and follow the instructions. ----- Steam