From 467220e6d7bae60130b460b9102600ea55f4003a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 20 Jan 2024 16:50:51 +0100 Subject: [PATCH 01/26] Base GetColliding script functions on collisions detected by the movement solver --- CHANGELOG.md | 1 + apps/openmw/mwphysics/movementsolver.cpp | 18 ++++++++++++++---- apps/openmw/mwphysics/object.cpp | 17 +++++++++++++++++ apps/openmw/mwphysics/object.hpp | 12 ++++++++++++ apps/openmw/mwphysics/physicssystem.cpp | 19 +++++++++++++------ apps/openmw/mwphysics/physicssystem.hpp | 9 +++++---- apps/openmw/mwworld/worldimp.cpp | 7 ++----- 7 files changed, 64 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b014ca0389..9b5a75229d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ Bug #7034: Misc items defined in one content file are not treated as keys if another content file uses them as such Bug #7042: Weapon follow animations that immediately follow the hit animations cause multiple hits Bug #7044: Changing a class' services does not affect autocalculated NPCs + Bug #7053: Running into objects doesn't trigger GetCollidingPC Bug #7054: Quests aren't sorted by name Bug #7064: NPCs don't report crime if the player is casting offensive spells on them while sneaking Bug #7077: OpenMW fails to load certain particle effects in .osgt format diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index c0b5014b31..fe5c1a955e 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -15,6 +15,7 @@ #include "collisiontype.hpp" #include "constants.hpp" #include "contacttestwrapper.h" +#include "object.hpp" #include "physicssystem.hpp" #include "projectile.hpp" #include "projectileconvexcallback.hpp" @@ -243,11 +244,20 @@ namespace MWPhysics float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ; osg::Vec3f oldPosition = newPosition; bool usedStepLogic = false; - if (hitHeight < Constants::sStepSizeUp && !isActor(tracer.mHitObject)) + if (!isActor(tracer.mHitObject)) { - // Try to step up onto it. - // NOTE: this modifies newPosition and velocity on its own if successful - usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0); + if (hitHeight < Constants::sStepSizeUp) + { + // Try to step up onto it. + // NOTE: this modifies newPosition and velocity on its own if successful + usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0); + } + auto* ptrHolder = static_cast(tracer.mHitObject->getUserPointer()); + if (Object* hitObject = dynamic_cast(ptrHolder)) + { + hitObject->addCollision( + actor.mIsPlayer ? ScriptedCollisionType_Player : ScriptedCollisionType_Actor); + } } if (usedStepLogic) { diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 9c97ac7c32..5936083f1d 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -23,6 +23,7 @@ namespace MWPhysics , mPosition(ptr.getRefData().getPosition().asVec3()) , mRotation(rotation) , mTaskScheduler(scheduler) + , mCollidedWith(ScriptedCollisionType_None) { mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(), Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation)); @@ -166,4 +167,20 @@ namespace MWPhysics } return result; } + + bool Object::collidedWith(ScriptedCollisionType type) const + { + return mCollidedWith & type; + } + + void Object::addCollision(ScriptedCollisionType type) + { + std::unique_lock lock(mPositionMutex); + mCollidedWith |= type; + } + + void Object::resetCollisions() + { + mCollidedWith = ScriptedCollisionType_None; + } } diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index 875f12d879..15b7c46926 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -18,6 +18,14 @@ namespace MWPhysics { class PhysicsTaskScheduler; + enum ScriptedCollisionType : char + { + ScriptedCollisionType_None = 0, + ScriptedCollisionType_Actor = 1, + // Note that this isn't 3, colliding with a player doesn't count as colliding with an actor + ScriptedCollisionType_Player = 2 + }; + class Object final : public PtrHolder { public: @@ -38,6 +46,9 @@ namespace MWPhysics /// @brief update object shape /// @return true if shape changed bool animateCollisionShapes(); + bool collidedWith(ScriptedCollisionType type) const; + void addCollision(ScriptedCollisionType type); + void resetCollisions(); private: osg::ref_ptr mShapeInstance; @@ -50,6 +61,7 @@ namespace MWPhysics bool mTransformUpdatePending = false; mutable std::mutex mPositionMutex; PhysicsTaskScheduler* mTaskScheduler; + char mCollidedWith; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2196834a50..149113dfb1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -673,12 +673,13 @@ namespace MWPhysics // Slow fall reduces fall speed by a factor of (effect magnitude / 200) const float slowFall = 1.f - std::clamp(effects.getOrDefault(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f, 0.f, 1.f); - const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); + const bool isPlayer = ptr == world->getPlayerConstPtr(); + const bool godmode = isPlayer && world->getGodModeState(); const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Paralyze).getModifier() > 0); simulations.emplace_back(ActorSimulation{ - physicActor, ActorFrameData{ *physicActor, inert, waterCollision, slowFall, waterlevel } }); + physicActor, ActorFrameData{ *physicActor, inert, waterCollision, slowFall, waterlevel, isPlayer } }); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) @@ -708,6 +709,8 @@ namespace MWPhysics changed = false; } } + for (auto& [_, object] : mObjects) + object->resetCollisions(); #ifndef BT_NO_PROFILE CProfileManager::Reset(); @@ -782,10 +785,12 @@ namespace MWPhysics } } - bool PhysicsSystem::isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const + bool PhysicsSystem::isObjectCollidingWith(const MWWorld::ConstPtr& object, ScriptedCollisionType type) const { - std::vector collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); - return (std::find(collisions.begin(), collisions.end(), actor) != collisions.end()); + auto found = mObjects.find(object.mRef); + if (found != mObjects.end()) + return found->second->collidedWith(type); + return false; } void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector& out) const @@ -890,7 +895,8 @@ namespace MWPhysics mDebugDrawer->addCollision(position, normal); } - ActorFrameData::ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel) + ActorFrameData::ActorFrameData( + Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel, bool isPlayer) : mPosition() , mStandingOn(nullptr) , mIsOnGround(actor.getOnGround()) @@ -917,6 +923,7 @@ namespace MWPhysics , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) , mWaterCollision(waterCollision) , mSkipCollisionDetection(!actor.getCollisionMode()) + , mIsPlayer(isPlayer) { } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ad56581eb3..6734682092 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -56,6 +56,7 @@ namespace MWPhysics class Actor; class PhysicsTaskScheduler; class Projectile; + enum ScriptedCollisionType : char; using ActorMap = std::unordered_map>; @@ -79,7 +80,7 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel); + ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel, bool isPlayer); osg::Vec3f mPosition; osg::Vec3f mInertia; const btCollisionObject* mStandingOn; @@ -102,6 +103,7 @@ namespace MWPhysics const bool mIsAquatic; const bool mWaterCollision; const bool mSkipCollisionDetection; + const bool mIsPlayer; }; struct ProjectileFrameData @@ -258,9 +260,8 @@ namespace MWPhysics /// Get the handle of all actors standing on \a object in this frame. void getActorsStandingOn(const MWWorld::ConstPtr& object, std::vector& out) const; - /// Return true if \a actor has collided with \a object in this frame. - /// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. - bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const; + /// Return true if an object of the given type has collided with this object + bool isObjectCollidingWith(const MWWorld::ConstPtr& object, ScriptedCollisionType type) const; /// Get the handle of all actors colliding with \a object in this frame. void getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector& out) const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1b6af6038e..701895e4f8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2425,15 +2425,12 @@ namespace MWWorld bool World::getPlayerCollidingWith(const MWWorld::ConstPtr& object) { - MWWorld::Ptr player = getPlayerPtr(); - return mPhysics->isActorCollidingWith(player, object); + return mPhysics->isObjectCollidingWith(object, MWPhysics::ScriptedCollisionType_Player); } bool World::getActorCollidingWith(const MWWorld::ConstPtr& object) { - std::vector actors; - mPhysics->getActorsCollidingWith(object, actors); - return !actors.empty(); + return mPhysics->isObjectCollidingWith(object, MWPhysics::ScriptedCollisionType_Actor); } void World::hurtStandingActors(const ConstPtr& object, float healthPerSecond) From 2575801ba2a1367d151acef3b8a024cbcf51e702 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 22 Jan 2024 09:52:32 +0400 Subject: [PATCH 02/26] Improve esmtool output --- apps/esmtool/esmtool.cpp | 4 ++-- apps/esmtool/record.cpp | 42 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 13f222ed72..0473676f93 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -69,7 +69,7 @@ Allowed options)"); addOption("name,n", bpo::value(), "Show only the record with this name. Only affects dump mode."); addOption("plain,p", "Print contents of dialogs, books and scripts. " - "(skipped by default)" + "(skipped by default) " "Only affects dump mode."); addOption("quiet,q", "Suppress all record information. Useful for speed tests."); addOption("loadcells,C", "Browse through contents of all cells."); @@ -390,7 +390,7 @@ namespace if (!quiet && interested) { - std::cout << "\nRecord: " << n.toStringView() << " '" << record->getId() << "'\n" + std::cout << "\nRecord: " << n.toStringView() << " " << record->getId() << "\n" << "Record flags: " << recordFlags(record->getFlags()) << '\n'; record->print(); } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 044fbf9f93..e3b81daf41 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -464,7 +464,8 @@ namespace EsmTool { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } @@ -516,7 +517,8 @@ namespace EsmTool std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; @@ -679,7 +681,8 @@ namespace EsmTool { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Flags: " << creatureFlags((int)mData.mFlags) << std::endl; std::cout << " Blood Type: " << mData.mBloodType + 1 << std::endl; std::cout << " Original: " << mData.mOriginal << std::endl; @@ -747,7 +750,8 @@ namespace EsmTool { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; std::cout << " OpenSound: " << mData.mOpenSound << std::endl; std::cout << " CloseSound: " << mData.mCloseSound << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; @@ -1338,28 +1342,26 @@ namespace EsmTool template <> void Record::print() { - std::cout << " Id:" << std::endl; - std::cout << " CellId: " << mData.mCellState.mId << std::endl; - std::cout << " Index:" << std::endl; - std::cout << " WaterLevel: " << mData.mCellState.mWaterLevel << std::endl; - std::cout << " HasFogOfWar: " << mData.mCellState.mHasFogOfWar << std::endl; - std::cout << " LastRespawn:" << std::endl; + std::cout << " Cell Id: \"" << mData.mCellState.mId.toString() << "\"" << std::endl; + std::cout << " Water Level: " << mData.mCellState.mWaterLevel << std::endl; + std::cout << " Has Fog Of War: " << mData.mCellState.mHasFogOfWar << std::endl; + std::cout << " Last Respawn:" << std::endl; std::cout << " Day:" << mData.mCellState.mLastRespawn.mDay << std::endl; std::cout << " Hour:" << mData.mCellState.mLastRespawn.mHour << std::endl; if (mData.mCellState.mHasFogOfWar) { - std::cout << " NorthMarkerAngle: " << mData.mFogState.mNorthMarkerAngle << std::endl; + std::cout << " North Marker Angle: " << mData.mFogState.mNorthMarkerAngle << std::endl; std::cout << " Bounds:" << std::endl; - std::cout << " MinX: " << mData.mFogState.mBounds.mMinX << std::endl; - std::cout << " MinY: " << mData.mFogState.mBounds.mMinY << std::endl; - std::cout << " MaxX: " << mData.mFogState.mBounds.mMaxX << std::endl; - std::cout << " MaxY: " << mData.mFogState.mBounds.mMaxY << std::endl; + std::cout << " Min X: " << mData.mFogState.mBounds.mMinX << std::endl; + std::cout << " Min Y: " << mData.mFogState.mBounds.mMinY << std::endl; + std::cout << " Max X: " << mData.mFogState.mBounds.mMaxX << std::endl; + std::cout << " Max Y: " << mData.mFogState.mBounds.mMaxY << std::endl; for (const ESM::FogTexture& fogTexture : mData.mFogState.mFogTextures) { - std::cout << " FogTexture:" << std::endl; + std::cout << " Fog Texture:" << std::endl; std::cout << " X: " << fogTexture.mX << std::endl; std::cout << " Y: " << fogTexture.mY << std::endl; - std::cout << " ImageData: (" << fogTexture.mImageData.size() << ")" << std::endl; + std::cout << " Image Data: (" << fogTexture.mImageData.size() << ")" << std::endl; } } } @@ -1367,7 +1369,7 @@ namespace EsmTool template <> std::string Record::getId() const { - return mData.mName; + return std::string(); // No ID for Cell record } template <> @@ -1397,9 +1399,7 @@ namespace EsmTool template <> std::string Record::getId() const { - std::ostringstream stream; - stream << mData.mCellState.mId; - return stream.str(); + return std::string(); // No ID for CellState record } } // end namespace From 54429cd23bf7118aac2e475d0221fe927f29ba32 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 24 Jan 2024 18:31:04 +0100 Subject: [PATCH 03/26] Parse special characters that have been put back as names too --- .../mwscript/test_scripts.cpp | 28 +++++++++++++++++++ components/compiler/scanner.cpp | 15 +++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index dbed262235..0de542bdc6 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -326,6 +326,8 @@ End)mwscript"; Addtopic -spells... Addtopic -magicka... +player->PositionCell, -97274, -94273, 8064, -12,-12 + End)mwscript"; const std::string sIssue4061 = R"mwscript(Begin 01_Rz_neuvazhay-koryto2 @@ -763,7 +765,33 @@ End)mwscript"; mTopics.erase(mTopics.begin()); } }; + class PositionCell : public Interpreter::Opcode0 + { + public: + void execute(Interpreter::Runtime& runtime) + { + std::string_view target = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float zRot = runtime[0].mFloat; + runtime.pop(); + std::string_view cellID = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + EXPECT_EQ(target, "player"); + EXPECT_EQ(x, -97274); + EXPECT_EQ(y, -94273); + EXPECT_EQ(z, 8064); + EXPECT_EQ(zRot, -12); + EXPECT_EQ(cellID, "-12"); + } + }; installOpcode(Compiler::Dialogue::opcodeAddTopic, topics); + installOpcode(Compiler::Transformation::opcodePositionCellExplicit); TestInterpreterContext context; run(*script, context); EXPECT_TRUE(topics.empty()); diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 4794f569b5..49f3999740 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -63,9 +63,22 @@ namespace Compiler switch (mPutback) { case Putback_Special: - + { mPutback = Putback_None; + // Replicate behaviour from scanSpecial so putting something back doesn't change the way it's handled + if (mExpectName && (mPutbackCode == S_member || mPutbackCode == S_minus)) + { + mExpectName = false; + bool cont = false; + bool tolerant = mTolerantNames; + mTolerantNames = true; + MultiChar c{ mPutbackCode == S_member ? '.' : '-' }; + scanName(c, parser, cont); + mTolerantNames = tolerant; + return cont; + } return parser.parseSpecial(mPutbackCode, mPutbackLoc, *this); + } case Putback_Integer: From 199d97d32a49e285cc3b19764c1db45193889126 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 17 Jan 2024 22:57:53 +0100 Subject: [PATCH 04/26] Use forward declaration for VFS::Manager --- components/esm4/reader.cpp | 1 + components/esm4/reader.hpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index ce7f534786..a3ea438d65 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include "grouptype.hpp" diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index c63dbd1548..92dc00b96d 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -36,13 +36,17 @@ #include #include -#include namespace ToUTF8 { class StatelessUtf8Encoder; } +namespace VFS +{ + class Manager; +} + namespace ESM4 { #pragma pack(push, 1) From d549cfd66b1132d7835c600a5e91a4e16b9ef7d4 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 16 Jan 2024 23:53:55 +0100 Subject: [PATCH 05/26] Check path for being normalized --- components/vfs/manager.cpp | 2 ++ components/vfs/pathutil.hpp | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index d312ce9d84..1bf789402d 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -1,6 +1,7 @@ #include "manager.hpp" #include +#include #include #include @@ -44,6 +45,7 @@ namespace VFS Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const { + assert(Path::isNormalized(normalizedName)); const auto found = mIndex.find(normalizedName); if (found == mIndex.end()) throw std::runtime_error("Resource '" + std::string(normalizedName) + "' not found"); diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 9bcc263842..0856bfffa2 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -15,6 +15,11 @@ namespace VFS::Path return c == '\\' ? '/' : Misc::StringUtils::toLower(c); } + inline constexpr bool isNormalized(std::string_view name) + { + return std::all_of(name.begin(), name.end(), [](char v) { return v == normalize(v); }); + } + inline void normalizeFilenameInPlace(std::string& name) { std::transform(name.begin(), name.end(), name.begin(), normalize); From 9279138fb0c9e138846b259ea0e566f4c8ae01d7 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 16 Jan 2024 23:55:42 +0100 Subject: [PATCH 06/26] Accept normalized path by VFS::Manager functions --- components/vfs/manager.cpp | 13 ++++++------- components/vfs/manager.hpp | 7 ++++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 1bf789402d..a6add0861a 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -38,9 +38,9 @@ namespace VFS archive->listResources(mIndex); } - Files::IStreamPtr Manager::get(std::string_view name) const + Files::IStreamPtr Manager::get(const Path::Normalized& name) const { - return getNormalized(Path::normalizeFilename(name)); + return getNormalized(name); } Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const @@ -52,17 +52,16 @@ namespace VFS return found->second->open(); } - bool Manager::exists(std::string_view name) const + bool Manager::exists(const Path::Normalized& name) const { - return mIndex.find(Path::normalizeFilename(name)) != mIndex.end(); + return mIndex.find(name) != mIndex.end(); } - std::string Manager::getArchive(std::string_view name) const + std::string Manager::getArchive(const Path::Normalized& name) const { - std::string normalized = Path::normalizeFilename(name); for (auto it = mArchives.rbegin(); it != mArchives.rend(); ++it) { - if ((*it)->contains(normalized)) + if ((*it)->contains(name)) return (*it)->getDescription(); } return {}; diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 05990a8607..7598b77e68 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -10,6 +10,7 @@ #include #include "filemap.hpp" +#include "pathutil.hpp" namespace VFS { @@ -40,19 +41,19 @@ namespace VFS /// Does a file with this name exist? /// @note May be called from any thread once the index has been built. - bool exists(std::string_view name) const; + bool exists(const Path::Normalized& name) const; /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. - Files::IStreamPtr get(std::string_view name) const; + Files::IStreamPtr get(const Path::Normalized& name) const; /// Retrieve a file by name (name is already normalized). /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. Files::IStreamPtr getNormalized(std::string_view normalizedName) const; - std::string getArchive(std::string_view name) const; + std::string getArchive(const Path::Normalized& name) const; /// Recursively iterate over the elements of the given path /// In practice it return all files of the VFS starting with the given path From 70061329a11c6a76cf79dce30aae959d537b4594 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 17 Jan 2024 23:32:15 +0100 Subject: [PATCH 07/26] Return Path::Normalized from RecursiveDirectoryIterator --- apps/niftest/niftest.cpp | 4 ++-- apps/opencs/model/world/resources.cpp | 15 +++++++-------- components/vfs/recursivedirectoryiterator.hpp | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 9352651030..ee6e84295f 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -113,9 +113,9 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat for (const auto& name : vfs.getRecursiveDirectoryIterator("")) { - if (isNIF(name)) + if (isNIF(name.value())) { - readNIF(archivePath, name, &vfs, quiet); + readNIF(archivePath, name.value(), &vfs, quiet); } } diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 345f6008ec..9957af3d66 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -30,18 +30,18 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) { - if (filepath.size() < baseSize + 1 || filepath.substr(0, baseSize) != mBaseDirectory - || (filepath[baseSize] != '/' && filepath[baseSize] != '\\')) + const std::string_view view = filepath.view(); + if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/') continue; if (extensions) { - std::string::size_type extensionIndex = filepath.find_last_of('.'); + const auto extensionIndex = view.find_last_of('.'); - if (extensionIndex == std::string::npos) + if (extensionIndex == std::string_view::npos) continue; - std::string extension = filepath.substr(extensionIndex + 1); + std::string_view extension = view.substr(extensionIndex + 1); int i = 0; @@ -53,10 +53,9 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e continue; } - std::string file = filepath.substr(baseSize + 1); + std::string file(view.substr(baseSize + 1)); mFiles.push_back(file); - std::replace(file.begin(), file.end(), '\\', '/'); - mIndex.insert(std::make_pair(Misc::StringUtils::lowerCase(file), static_cast(mFiles.size()) - 1)); + mIndex.emplace(std::move(file), static_cast(mFiles.size()) - 1); } } diff --git a/components/vfs/recursivedirectoryiterator.hpp b/components/vfs/recursivedirectoryiterator.hpp index 39fb26e873..3410a2092b 100644 --- a/components/vfs/recursivedirectoryiterator.hpp +++ b/components/vfs/recursivedirectoryiterator.hpp @@ -16,9 +16,9 @@ namespace VFS { } - const std::string& operator*() const { return mIt->first.value(); } + const Path::Normalized& operator*() const { return mIt->first; } - const std::string* operator->() const { return &mIt->first.value(); } + const Path::Normalized* operator->() const { return &mIt->first; } RecursiveDirectoryIterator& operator++() { From ad64c7175351ae81e3111983533b7a5bca804b58 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 26 Jan 2024 17:02:13 +0300 Subject: [PATCH 08/26] Correct MaxNumberRipples and Timescale Clouds validation categories --- components/fallback/validate.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index 8dfcd97570..571f0f9b19 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -7,12 +7,12 @@ static const std::set allowedKeysInt = { "LightAttenuation_LinearMethod", "LightAttenuation_OutQuadInLin", "LightAttenuation_QuadraticMethod", "LightAttenuation_UseConstant", - "LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_NearWaterRadius", "Water_NearWaterPoints", - "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", "Weather_Clear_Using_Precip", - "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", "Weather_Overcast_Using_Precip", - "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", "Weather_Ashstorm_Using_Precip", - "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples", - "Weather_Snow_Ripples" }; + "LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_MaxNumberRipples", "Water_NearWaterRadius", + "Water_NearWaterPoints", "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", + "Weather_Clear_Using_Precip", "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", + "Weather_Overcast_Using_Precip", "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", + "Weather_Ashstorm_Using_Precip", "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", + "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples", "Weather_Snow_Ripples", "Weather_Timescale_Clouds" }; static const std::set allowedKeysFloat = { "General_Werewolf_FOV", "Inventory_DirectionalAmbientB", "Inventory_DirectionalAmbientG", "Inventory_DirectionalAmbientR", "Inventory_DirectionalDiffuseB", @@ -186,7 +186,7 @@ static const std::set allowedKeysNonNumeric = { "Blood_Model_0 "Weather_Thunderstorm_Sun_Disc_Sunset_Color", "Weather_Thunderstorm_Sun_Night_Color", "Weather_Thunderstorm_Sun_Sunrise_Color", "Weather_Thunderstorm_Sun_Sunset_Color", "Weather_Thunderstorm_Thunder_Sound_ID_0", "Weather_Thunderstorm_Thunder_Sound_ID_1", - "Weather_Thunderstorm_Thunder_Sound_ID_2", "Weather_Thunderstorm_Thunder_Sound_ID_3", "Weather_Timescale_Clouds", + "Weather_Thunderstorm_Thunder_Sound_ID_2", "Weather_Thunderstorm_Thunder_Sound_ID_3", "Weather_Clear_Thunder_Sound_ID_0", "Weather_Clear_Thunder_Sound_ID_1", "Weather_Clear_Thunder_Sound_ID_2", "Weather_Clear_Thunder_Sound_ID_3", "Weather_Cloudy_Thunder_Sound_ID_0", "Weather_Cloudy_Thunder_Sound_ID_1", "Weather_Cloudy_Thunder_Sound_ID_2", "Weather_Cloudy_Thunder_Sound_ID_3", "Weather_Foggy_Thunder_Sound_ID_0", @@ -218,7 +218,7 @@ static const std::set allowedKeysUnused = { "Inventory_Uniform "Map_Travel_Boat_Blue", "Map_Travel_Boat_Green", "Map_Travel_Boat_Red", "Map_Travel_Magic_Blue", "Map_Travel_Magic_Green", "Map_Travel_Magic_Red", "Map_Travel_Siltstrider_Blue", "Map_Travel_Siltstrider_Green", "Map_Travel_Siltstrider_Red", "Movies_Game_Logo", "PixelWater_Resolution", "PixelWater_SurfaceFPS", - "PixelWater_TileCount", "Movies_Loading", "Movies_Options_Menu", "Movies_Project_Logo", "Water_MaxNumberRipples", + "PixelWater_TileCount", "Movies_Loading", "Movies_Options_Menu", "Movies_Project_Logo", "Water_NearWaterUnderwaterFreq", "Water_NearWaterUnderwaterVolume", "Water_PSWaterReflectTerrain", "Water_PSWaterReflectUpdate", "Water_RippleAlphas", "Water_RippleScale", "Water_SurfaceTextureSize", "Water_TileTextureDivisor", "Weather_AlphaReduce", "Weather_Ashstorm_Storm_Threshold", From 2ea4013382e43fa24c733689a06165617c481626 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 26 Jan 2024 15:32:14 +0300 Subject: [PATCH 09/26] Correct base cloud speed, support Timescale Clouds fallback setting (#7792) --- CHANGELOG.md | 1 + apps/openmw/mwrender/sky.cpp | 15 ++++++++++++--- apps/openmw/mwrender/sky.hpp | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b014ca0389..4e162ded2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -173,6 +173,7 @@ Feature #7652: Sort inactive post processing shaders list properly Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher + Feature #7792: Support Timescale Clouds Task #5896: Do not use deprecated MyGUI properties Task #6624: Drop support for saves made prior to 0.45 Task #7113: Move from std::atoi to std::from_char diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 060b6ee5de..3efddc4ba3 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -240,6 +240,7 @@ namespace MWRender , mIsStorm(false) , mDay(0) , mMonth(0) + , mTimescaleClouds(Fallback::Map::getBool("Weather_Timescale_Clouds")) , mCloudAnimationTimer(0.f) , mRainTimer(0.f) , mStormParticleDirection(MWWorld::Weather::defaultDirection()) @@ -557,8 +558,17 @@ namespace MWRender mParticleNode->setAttitude(quat); } + const float timeScale = MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale(); + // UV Scroll the clouds - mCloudAnimationTimer += duration * mCloudSpeed * 0.003; + float cloudDelta = duration * mCloudSpeed / 400.f; + if (mTimescaleClouds) + cloudDelta *= timeScale / 60.f; + + mCloudAnimationTimer += cloudDelta; + if (mCloudAnimationTimer >= 4.f) + mCloudAnimationTimer -= 4.f; + mNextCloudUpdater->setTextureCoord(mCloudAnimationTimer); mCloudUpdater->setTextureCoord(mCloudAnimationTimer); @@ -574,8 +584,7 @@ namespace MWRender } // rotate the stars by 360 degrees every 4 days - mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale() * duration - * osg::DegreesToRadians(360.f) / (3600 * 96.f); + mAtmosphereNightRoll += timeScale * duration * osg::DegreesToRadians(360.f) / (3600 * 96.f); if (mAtmosphereNightNode->getNodeMask() != 0) mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1))); mPrecipitationOccluder->update(); diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 96af2e6ec9..e0cd26f98a 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -165,6 +165,7 @@ namespace MWRender int mDay; int mMonth; + bool mTimescaleClouds; float mCloudAnimationTimer; float mRainTimer; From 23e30eaaa5d9932188e35ea3a4720f29e5160d9d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 26 Jan 2024 16:39:50 +0300 Subject: [PATCH 10/26] Support MaxNumberRipples setting (#7795) --- CHANGELOG.md | 1 + apps/openmw/mwrender/ripplesimulation.cpp | 35 +++++++++++++++++++++-- apps/openmw/mwrender/ripplesimulation.hpp | 2 ++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e162ded2d..3f4106770f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -174,6 +174,7 @@ Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher Feature #7792: Support Timescale Clouds + Feature #7795: Support MaxNumberRipples INI setting Task #5896: Do not use deprecated MyGUI properties Task #6624: Drop support for saves made prior to 0.45 Task #7113: Move from std::atoi to std::from_char diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index abfb7ba9cd..b7661ca664 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -80,6 +80,25 @@ namespace node->setStateSet(stateset); } + + int findOldestParticleAlive(const osgParticle::ParticleSystem* partsys) + { + int oldest = -1; + float oldestAge = 0.f; + for (int i = 0; i < partsys->numParticles(); ++i) + { + const osgParticle::Particle* particle = partsys->getParticle(i); + if (!particle->isAlive()) + continue; + const float age = particle->getAge(); + if (oldest == -1 || age > oldestAge) + { + oldest = i; + oldestAge = age; + } + } + return oldest; + } } namespace MWRender @@ -87,6 +106,7 @@ namespace MWRender RippleSimulation::RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem) : mParent(parent) + , mMaxNumberRipples(Fallback::Map::getInt("Water_MaxNumberRipples")) { mParticleSystem = new osgParticle::ParticleSystem; @@ -159,9 +179,6 @@ namespace MWRender currentPos.z() = mParticleNode->getPosition().z(); - if (mParticleSystem->numParticles() - mParticleSystem->numDeadParticles() > 500) - continue; // TODO: remove the oldest particle to make room? - emitRipple(currentPos); } } @@ -226,7 +243,19 @@ namespace MWRender } else { + if (mMaxNumberRipples == 0) + return; + osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex()); + if (mParticleSystem->numParticles() - mParticleSystem->numDeadParticles() > mMaxNumberRipples) + { + // osgParticle::ParticleSystem design requires this to be O(N) + // However, the number of particles we'll have to go through is not large + // If the user makes the limit absurd and manages to actually hit it this could be a problem + const int oldest = findOldestParticleAlive(mParticleSystem); + if (oldest != -1) + mParticleSystem->reuseParticle(oldest); + } osgParticle::Particle* p = mParticleSystem->createParticle(nullptr); p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f)); p->setAngle(osg::Vec3f(0, 0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index 1f09797bf4..10b47f5679 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -74,6 +74,8 @@ namespace MWRender std::vector mEmitters; Ripples* mRipples = nullptr; + + int mMaxNumberRipples; }; } From 48bbf0b637dc5ad001ee9e989a03e06cbc6a7200 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 26 Jan 2024 19:09:52 +0300 Subject: [PATCH 11/26] Editor: Don't complain about body part references in Verify --- apps/opencs/model/tools/referencecheck.cpp | 8 ++++++-- apps/opencs/model/tools/referencecheck.hpp | 5 ++++- apps/opencs/model/tools/tools.cpp | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/tools/referencecheck.cpp b/apps/opencs/model/tools/referencecheck.cpp index 511458b946..33c2168ef3 100644 --- a/apps/opencs/model/tools/referencecheck.cpp +++ b/apps/opencs/model/tools/referencecheck.cpp @@ -18,16 +18,18 @@ #include #include +#include #include CSMTools::ReferenceCheckStage::ReferenceCheckStage(const CSMWorld::RefCollection& references, const CSMWorld::RefIdCollection& referencables, const CSMWorld::IdCollection& cells, - const CSMWorld::IdCollection& factions) + const CSMWorld::IdCollection& factions, const CSMWorld::IdCollection& bodyparts) : mReferences(references) , mObjects(referencables) , mDataSet(referencables.getDataSet()) , mCells(cells) , mFactions(factions) + , mBodyParts(bodyparts) { mIgnoreBaseRecords = false; } @@ -49,9 +51,11 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages& message else { // Check for non existing referenced object - if (mObjects.searchId(cellRef.mRefID) == -1) + if (mObjects.searchId(cellRef.mRefID) == -1 && mBodyParts.searchId(cellRef.mRefID) == -1) + { messages.add(id, "Instance of a non-existent object '" + cellRef.mRefID.getRefIdString() + "'", "", CSMDoc::Message::Severity_Error); + } else { // Check if reference charge is valid for it's proper referenced type diff --git a/apps/opencs/model/tools/referencecheck.hpp b/apps/opencs/model/tools/referencecheck.hpp index 04cca2b803..5f5004b912 100644 --- a/apps/opencs/model/tools/referencecheck.hpp +++ b/apps/opencs/model/tools/referencecheck.hpp @@ -8,6 +8,7 @@ namespace ESM { + struct BodyPart; struct Faction; } @@ -29,7 +30,8 @@ namespace CSMTools { public: ReferenceCheckStage(const CSMWorld::RefCollection& references, const CSMWorld::RefIdCollection& referencables, - const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& factions); + const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& bodyparts); void perform(int stage, CSMDoc::Messages& messages) override; int setup() override; @@ -40,6 +42,7 @@ namespace CSMTools const CSMWorld::RefIdData& mDataSet; const CSMWorld::IdCollection& mCells; const CSMWorld::IdCollection& mFactions; + const CSMWorld::IdCollection& mBodyParts; bool mIgnoreBaseRecords; }; } diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index e1e7a0dcd8..04548ca4cf 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -105,8 +105,8 @@ CSMDoc::OperationHolder* CSMTools::Tools::getVerifier() mData.getFactions(), mData.getScripts(), mData.getResources(CSMWorld::UniversalId::Type_Meshes), mData.getResources(CSMWorld::UniversalId::Type_Icons), mData.getBodyParts())); - mVerifierOperation->appendStage(new ReferenceCheckStage( - mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions())); + mVerifierOperation->appendStage(new ReferenceCheckStage(mData.getReferences(), mData.getReferenceables(), + mData.getCells(), mData.getFactions(), mData.getBodyParts())); mVerifierOperation->appendStage(new ScriptCheckStage(mDocument)); From 70a0b7ea9c5543bf81ddb7d51c892e90c77f91d6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 26 Jan 2024 19:23:03 +0300 Subject: [PATCH 12/26] Editor: Drop zero attribute warnings --- apps/opencs/model/tools/referenceablecheck.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index d25568fd0a..a692bc10d1 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -691,15 +691,6 @@ void CSMTools::ReferenceableCheckStage::npcCheck( return; } } - else if (npc.mNpdt.mHealth != 0) - { - for (size_t i = 0; i < npc.mNpdt.mAttributes.size(); ++i) - { - if (npc.mNpdt.mAttributes[i] == 0) - messages.add(id, ESM::Attribute::indexToRefId(i).getRefIdString() + " is equal to zero", {}, - CSMDoc::Message::Severity_Warning); - } - } if (level <= 0) messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); From 1d1ce2de7b332c25de68eb6e4847063ed91ee6eb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 26 Jan 2024 17:15:41 +0100 Subject: [PATCH 13/26] Use the correct id to absorb enchantments --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/activespells.cpp | 34 ++++++++++++++---------- apps/openmw/mwmechanics/activespells.hpp | 1 + apps/openmw/mwmechanics/spelleffects.cpp | 10 +++---- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b014ca0389..a2fbdc6eaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,7 @@ Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7770: Sword of the Perithia: Script execution failure Bug #7780: Non-ASCII texture paths in NIF files don't work + Bug #7796: Absorbed enchantments don't restore magicka Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index a9c669fce5..937e7a6658 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -174,6 +174,25 @@ namespace MWMechanics mWorsenings = -1; } + ESM::RefId ActiveSpells::ActiveSpellParams::getEnchantment() const + { + // Enchantment id is not stored directly. Instead the enchanted item is stored. + const auto& store = MWBase::Environment::get().getESMStore(); + switch (store->find(mId)) + { + case ESM::REC_ARMO: + return store->get().find(mId)->mEnchant; + case ESM::REC_BOOK: + return store->get().find(mId)->mEnchant; + case ESM::REC_CLOT: + return store->get().find(mId)->mEnchant; + case ESM::REC_WEAP: + return store->get().find(mId)->mEnchant; + default: + return {}; + } + } + void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration) { if (mIterating) @@ -438,21 +457,8 @@ namespace MWMechanics if (store->get().search(id) == nullptr) return false; - // Enchantment id is not stored directly. Instead the enchanted item is stored. return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { - switch (store->find(spell.mId)) - { - case ESM::REC_ARMO: - return store->get().find(spell.mId)->mEnchant == id; - case ESM::REC_BOOK: - return store->get().find(spell.mId)->mEnchant == id; - case ESM::REC_CLOT: - return store->get().find(spell.mId)->mEnchant == id; - case ESM::REC_WEAP: - return store->get().find(spell.mId)->mEnchant == id; - default: - return false; - } + return spell.getEnchantment() == id; }) != mSpells.end(); } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 87497d9d7a..a505b8990a 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -73,6 +73,7 @@ namespace MWMechanics const std::string& getDisplayName() const { return mDisplayName; } ESM::RefNum getItem() const { return mItem; } + ESM::RefId getEnchantment() const; // Increments worsenings count and sets the next timestamp void worsen(); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index ebf9933cfc..8c415949f5 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -279,7 +279,8 @@ namespace return false; } - void absorbSpell(const ESM::RefId& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + void absorbSpell(const MWMechanics::ActiveSpells::ActiveSpellParams& spellParams, const MWWorld::Ptr& caster, + const MWWorld::Ptr& target) { const auto& esmStore = *MWBase::Environment::get().getESMStore(); const ESM::Static* absorbStatic = esmStore.get().find(ESM::RefId::stringRefId("VFX_Absorb")); @@ -287,15 +288,14 @@ namespace if (animation && !absorbStatic->mModel.empty()) animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false); - const ESM::Spell* spell = esmStore.get().search(spellId); int spellCost = 0; - if (spell) + if (const ESM::Spell* spell = esmStore.get().search(spellParams.getId())) { spellCost = MWMechanics::calcSpellCost(*spell); } else { - const ESM::Enchantment* enchantment = esmStore.get().search(spellId); + const ESM::Enchantment* enchantment = esmStore.get().search(spellParams.getEnchantment()); if (enchantment) spellCost = MWMechanics::getEffectiveEnchantmentCastCost(*enchantment, caster); } @@ -342,7 +342,7 @@ namespace { if (canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude) { - absorbSpell(spellParams.getId(), caster, target); + absorbSpell(spellParams, caster, target); return MWMechanics::MagicApplicationResult::Type::REMOVED; } } From 1288ec5cea93c201dfe0252d3c1a3db117d8b8a1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 27 Jan 2024 16:49:20 +0100 Subject: [PATCH 14/26] Use deserializeText for find and countOf --- apps/openmw/mwlua/objectbindings.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 47c55e86f0..2a30e31948 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -215,7 +215,7 @@ namespace MWLua objectT["recordId"] = sol::readonly_property( [](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().serializeText(); }); objectT["globalVariable"] = sol::readonly_property([](const ObjectT& o) -> sol::optional { - std::string globalVariable = o.ptr().getCellRef().getGlobalVariable(); + std::string_view globalVariable = o.ptr().getCellRef().getGlobalVariable(); if (globalVariable.empty()) return sol::nullopt; else @@ -619,7 +619,7 @@ namespace MWLua inventoryT["countOf"] = [](const InventoryT& inventory, std::string_view recordId) { const MWWorld::Ptr& ptr = inventory.mObj.ptr(); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); - return store.count(ESM::RefId::stringRefId(recordId)); + return store.count(ESM::RefId::deserializeText(recordId)); }; if constexpr (std::is_same_v) { @@ -637,7 +637,7 @@ namespace MWLua inventoryT["find"] = [](const InventoryT& inventory, std::string_view recordId) -> sol::optional { const MWWorld::Ptr& ptr = inventory.mObj.ptr(); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); - auto itemId = ESM::RefId::stringRefId(recordId); + auto itemId = ESM::RefId::deserializeText(recordId); for (const MWWorld::Ptr& item : store) { if (item.getCellRef().getRefId() == itemId) @@ -651,7 +651,7 @@ namespace MWLua inventoryT["findAll"] = [](const InventoryT& inventory, std::string_view recordId) { const MWWorld::Ptr& ptr = inventory.mObj.ptr(); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); - auto itemId = ESM::RefId::stringRefId(recordId); + auto itemId = ESM::RefId::deserializeText(recordId); ObjectIdList list = std::make_shared>(); for (const MWWorld::Ptr& item : store) { From 4fcacd59aa2e703e0cac0ef5cbd3538657fd962f Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 27 Jan 2024 12:02:56 -0600 Subject: [PATCH 15/26] Add model to NPC lua --- apps/openmw/mwlua/types/npc.cpp | 3 +++ files/lua_api/openmw/types.lua | 1 + 2 files changed, 4 insertions(+) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 30c8fd60d9..b1ac3d994a 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/mechanicsmanager.hpp" @@ -78,6 +79,8 @@ namespace MWLua = sol::readonly_property([](const ESM::NPC& rec) -> int { return (int)rec.mNpdt.mDisposition; }); record["head"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.serializeText(); }); + record["model"] = sol::readonly_property( + [](const ESM::NPC& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); addActorServicesBindings(record, context); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index ba0b2dd58b..734288bfb7 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -939,6 +939,7 @@ -- @field #string name -- @field #string race -- @field #string class Name of the NPC's class (e. g. Acrobat) +-- @field #string model Path to the model associated with this NPC, used for animations. -- @field #string mwscript MWScript that is attached to this NPC -- @field #string hair Path to the hair body part model -- @field #string head Path to the head body part model From 422e4551572569d0e26c04f6c09c9a941b018616 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 28 Jan 2024 05:38:12 +0300 Subject: [PATCH 16/26] Actually normalize the sun position exposed to post-processing --- components/fx/stateupdater.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/fx/stateupdater.hpp b/components/fx/stateupdater.hpp index f1a4cd89f3..9b7a25286a 100644 --- a/components/fx/stateupdater.hpp +++ b/components/fx/stateupdater.hpp @@ -43,6 +43,7 @@ namespace fx void setSunPos(const osg::Vec4f& pos, bool night) { mData.get() = pos; + mData.get().normalize(); if (night) mData.get().z() *= -1.f; From 43307bee28da92c55d46856e9cd2f4be6bbf24d1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 28 Jan 2024 11:25:46 +0400 Subject: [PATCH 17/26] Make ContentSelector errors localizable --- components/contentselector/model/contentmodel.cpp | 4 +++- components/contentselector/model/contentmodel.hpp | 4 ++++ components/contentselector/model/loadordererror.cpp | 12 ------------ components/contentselector/model/loadordererror.hpp | 1 - 4 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 components/contentselector/model/loadordererror.cpp diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 0aab06ac90..d800112712 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -723,8 +723,10 @@ QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const int index = indexFromItem(item(file->filePath())).row(); for (const LoadOrderError& error : checkForLoadOrderErrors(file, index)) { + assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None); + text += "

"; - text += error.toolTip(); + text += mErrorToolTips[error.errorCode() - 1].arg(error.fileName()); text += "

"; } text += (""); diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 1ba3090a32..f754b9ea30 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -93,6 +93,10 @@ namespace ContentSelectorModel QIcon mWarningIcon; bool mShowOMWScripts; + QString mErrorToolTips[ContentSelectorModel::LoadOrderError::ErrorCode_LoadOrder] + = { tr("Unable to find dependent file: %1"), tr("Dependent file needs to be active: %1"), + tr("This file needs to load after %1") }; + public: QString mMimeType; QStringList mMimeTypes; diff --git a/components/contentselector/model/loadordererror.cpp b/components/contentselector/model/loadordererror.cpp deleted file mode 100644 index c1b2025588..0000000000 --- a/components/contentselector/model/loadordererror.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "loadordererror.hpp" -#include - -QString ContentSelectorModel::LoadOrderError::sErrorToolTips[ErrorCode_LoadOrder] - = { QString("Unable to find dependent file: %1"), QString("Dependent file needs to be active: %1"), - QString("This file needs to load after %1") }; - -QString ContentSelectorModel::LoadOrderError::toolTip() const -{ - assert(mErrorCode); - return sErrorToolTips[mErrorCode - 1].arg(mFileName); -} diff --git a/components/contentselector/model/loadordererror.hpp b/components/contentselector/model/loadordererror.hpp index 8f47b6ed6a..b066ce4d4e 100644 --- a/components/contentselector/model/loadordererror.hpp +++ b/components/contentselector/model/loadordererror.hpp @@ -28,7 +28,6 @@ namespace ContentSelectorModel } inline ErrorCode errorCode() const { return mErrorCode; } inline QString fileName() const { return mFileName; } - QString toolTip() const; private: ErrorCode mErrorCode; From b83b30f0dc3b2827feea09841239455267dd37b8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 22 Jan 2024 19:06:51 +0300 Subject: [PATCH 18/26] Editor: Reset effect arguments when the effect ID changes (#7785) --- CHANGELOG.md | 1 + .../model/world/nestedcoladapterimp.hpp | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa220717d..7f92c39052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,7 @@ Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7770: Sword of the Perithia: Script execution failure Bug #7780: Non-ASCII texture paths in NIF files don't work + Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells Bug #7796: Absorbed enchantments don't restore magicka Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 235396c650..46928973fe 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -385,6 +385,26 @@ namespace CSMWorld case 0: { effect.mEffectID = static_cast(value.toInt()); + switch (effect.mEffectID) + { + case ESM::MagicEffect::DrainSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::AbsorbSkill: + effect.mAttribute = -1; + break; + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::AbsorbAttribute: + effect.mSkill = -1; + break; + default: + effect.mSkill = -1; + effect.mAttribute = -1; + } break; } case 1: From 36e1bdab1053bd2dd3ca8a46c98795afe0091600 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 27 Jan 2024 18:57:51 +0100 Subject: [PATCH 19/26] Use a smaller integer type instead of dealing with casting issues. --- apps/openmw/mwbase/luamanager.hpp | 2 +- apps/openmw/mwbase/mechanicsmanager.hpp | 6 ++--- apps/openmw/mwlua/animationbindings.cpp | 4 +-- apps/openmw/mwlua/luamanagerimp.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.hpp | 3 ++- apps/openmw/mwmechanics/actors.cpp | 4 +-- apps/openmw/mwmechanics/actors.hpp | 6 ++--- apps/openmw/mwmechanics/character.cpp | 25 ++++++++++--------- apps/openmw/mwmechanics/character.hpp | 6 ++--- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +-- .../mwmechanics/mechanicsmanagerimp.hpp | 6 ++--- apps/openmw/mwmechanics/objects.cpp | 6 ++--- apps/openmw/mwmechanics/objects.hpp | 4 +-- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/animation.hpp | 4 +-- apps/openmw/mwscript/animationextensions.cpp | 2 +- 16 files changed, 44 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 0503fcec9e..57886d5399 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -64,7 +64,7 @@ namespace MWBase virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0; virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, - std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) + std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void actorDied(const MWWorld::Ptr& actor) = 0; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 532100af7a..d7e377a0eb 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -171,7 +171,7 @@ namespace MWBase ///< Forces an object to refresh its animation state. virtual bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number = 1, bool scripted = false) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number = 1, bool scripted = false) = 0; ///< Run animation for a MW-reference. Calls to this function for references that are currently not /// in the scene should be ignored. @@ -180,8 +180,8 @@ namespace MWBase /// \param number How many times the animation should be run /// \param scripted Whether the animation should be treated as a scripted animation. /// \return Success or error - virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, - std::string_view startKey, std::string_view stopKey, bool forceLoop) + virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, + float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) = 0; ///< Lua variant of playAnimationGroup. The mode parameter is omitted /// and forced to 0. modes 1 and 2 can be emulated by doing clearAnimationQueue() and diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 272685dc11..ecd1e96dbb 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -249,7 +249,7 @@ namespace MWLua // Extended variant of MWScript's PlayGroup and LoopGroup api["playQueued"] = sol::overload( [mechanics](const sol::object& object, const std::string& groupname, const sol::table& options) { - int numberOfLoops = options.get_or("loops", std::numeric_limits::max()); + uint32_t numberOfLoops = options.get_or("loops", std::numeric_limits::max()); float speed = options.get_or("speed", 1.f); std::string startKey = options.get_or("startkey", "start"); std::string stopKey = options.get_or("stopkey", "stop"); @@ -265,7 +265,7 @@ namespace MWLua }); api["playBlended"] = [](const sol::object& object, std::string_view groupname, const sol::table& options) { - int loops = options.get_or("loops", 0); + uint32_t loops = options.get_or("loops", 0u); MWRender::Animation::AnimPriority priority = getPriorityArgument(options); BlendMask blendMask = options.get_or("blendmask", BlendMask::BlendMask_All); bool autoDisable = options.get_or("autodisable", true); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 89402a6008..ac5ae60f7d 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -373,7 +373,7 @@ namespace MWLua void LuaManager::playAnimation(const MWWorld::Ptr& actor, const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, - std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) + std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) { sol::table options = mLua.newTable(); options["blendmask"] = blendMask; diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 7556abad5d..a539d04348 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -82,7 +82,8 @@ namespace MWLua void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) override; void playAnimation(const MWWorld::Ptr& actor, const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, - std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) override; + std::string_view start, std::string_view stop, float startpoint, uint32_t loops, + bool loopfallback) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 92f8a212c9..a4e7168b0f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2005,7 +2005,7 @@ namespace MWMechanics } bool Actors::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) const + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) @@ -2020,7 +2020,7 @@ namespace MWMechanics } } - bool Actors::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + bool Actors::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) { const auto iter = mIndex.find(ptr.mRef); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 3ead5f069a..fe9f37511a 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -112,9 +112,9 @@ namespace MWMechanics void forceStateUpdate(const MWWorld::Ptr& ptr) const; - bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; - bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + bool playAnimationGroup(const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, + bool scripted = false) const; + bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop); void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void skipAnimation(const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5533cb578b..08a476631c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -483,7 +483,8 @@ namespace MWMechanics return; } - playBlendedAnimation(mCurrentHit, priority, MWRender::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul); + playBlendedAnimation(mCurrentHit, priority, MWRender::BlendMask_All, true, 1, startKey, stopKey, 0.0f, + std::numeric_limits::max()); } void CharacterController::refreshJumpAnims(JumpingState jump, bool force) @@ -521,7 +522,7 @@ namespace MWMechanics mCurrentJump = jumpAnimName; if (mJumpState == JumpState_InAir) playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, - startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul); + startAtLoop ? "loop start" : "start", "stop", 0.f, std::numeric_limits::max()); else if (mJumpState == JumpState_Landing) playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); } @@ -749,8 +750,8 @@ namespace MWMechanics } } - playBlendedAnimation( - mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true); + playBlendedAnimation(mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, + std::numeric_limits::max(), true); } void CharacterController::refreshIdleAnims(CharacterState idle, bool force) @@ -778,7 +779,7 @@ namespace MWMechanics } MWRender::Animation::AnimPriority priority = getIdlePriority(mIdleState); - size_t numLoops = std::numeric_limits::max(); + size_t numLoops = std::numeric_limits::max(); // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to // "idle"+weapon or "idle". @@ -1192,8 +1193,8 @@ namespace MWMechanics if (!animPlaying) { int mask = MWRender::BlendMask_Torso | MWRender::BlendMask_RightArm; - playBlendedAnimation( - "idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true); + playBlendedAnimation("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, + std::numeric_limits::max(), true); } else { @@ -1326,7 +1327,7 @@ namespace MWMechanics mAnimation->disable("shield"); playBlendedAnimation("torch", Priority_Torch, MWRender::BlendMask_LeftArm, false, 1.0f, "start", "stop", - 0.0f, std::numeric_limits::max(), true); + 0.0f, std::numeric_limits::max(), true); } else if (mAnimation->isPlaying("torch")) { @@ -2548,7 +2549,7 @@ namespace MWMechanics groupname, priority, blendMask, autodisable, speedmult, start, stop, startpoint, loops, loopfallback); } - bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool scripted) + bool CharacterController::playGroup(std::string_view groupname, int mode, uint32_t count, bool scripted) { if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; @@ -2585,7 +2586,7 @@ namespace MWMechanics // exactly x times, while non-actors will loop x+1 instead. if (mPtr.getClass().isActor()) count--; - count = std::max(count, 0); + count = std::max(count, 0u); AnimationQueueEntry entry; entry.mGroup = groupname; @@ -2620,7 +2621,7 @@ namespace MWMechanics } bool CharacterController::playGroupLua(std::string_view groupname, float speed, std::string_view startKey, - std::string_view stopKey, int loops, bool forceLoop) + std::string_view stopKey, uint32_t loops, bool forceLoop) { // Note: In mwscript, "idle" is a special case used to clear the anim queue. // In lua we offer an explicit clear method instead so this method does not treat "idle" special. @@ -2632,7 +2633,7 @@ namespace MWMechanics entry.mGroup = groupname; // Note: MWScript gives one less loop to actors than non-actors. // But this is the Lua version. We don't need to reproduce this weirdness here. - entry.mLoopCount = std::max(loops, 0); + entry.mLoopCount = std::max(loops, 0u); entry.mStartKey = startKey; entry.mStopKey = stopKey; entry.mLooping = mAnimation->isLoopingAnimation(groupname) || forceLoop; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index a507c73743..7146baeb0e 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -134,7 +134,7 @@ namespace MWMechanics struct AnimationQueueEntry { std::string mGroup; - size_t mLoopCount; + uint32_t mLoopCount; float mTime; bool mLooping; bool mScripted; @@ -277,9 +277,9 @@ namespace MWMechanics void playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback = false) const; - bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); + bool playGroup(std::string_view groupname, int mode, uint32_t count, bool scripted = false); bool playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey, - int loops, bool forceLoop); + uint32_t loops, bool forceLoop); void enableLuaAnimations(bool enable); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 5323f7e65c..9fd1b3ff8d 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -749,14 +749,14 @@ namespace MWMechanics } bool MechanicsManager::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted) { if (ptr.getClass().isActor()) return mActors.playAnimationGroup(ptr, groupName, mode, number, scripted); else return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted); } - bool MechanicsManager::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, + bool MechanicsManager::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) { if (ptr.getClass().isActor()) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 93c1fa3dc2..029d974ac2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -141,9 +141,9 @@ namespace MWMechanics /// Attempt to play an animation group /// @return Success or error - bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; - bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + bool playAnimationGroup(const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, + bool scripted = false) override; + bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) override; void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override; void skipAnimation(const MWWorld::Ptr& ptr) override; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 32d484df2f..12d342666b 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -99,7 +99,7 @@ namespace MWMechanics } bool Objects::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) @@ -114,8 +114,8 @@ namespace MWMechanics } } - bool Objects::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, - std::string_view startKey, std::string_view stopKey, bool forceLoop) + bool Objects::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, + float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 1fe43530b0..31c2768b1b 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -46,8 +46,8 @@ namespace MWMechanics void onClose(const MWWorld::Ptr& ptr); bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false); - bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted = false); + bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop); void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void skipAnimation(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 581d2843ab..9cdbb19a98 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -805,7 +805,7 @@ namespace MWRender } void Animation::play(std::string_view groupname, const AnimPriority& priority, int blendMask, bool autodisable, - float speedmult, std::string_view start, std::string_view stop, float startpoint, size_t loops, + float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) { if (!mObjectRoot || mAnimSources.empty()) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index dae81592b3..a2226a3054 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -153,7 +153,7 @@ namespace MWRender bool mPlaying; bool mLoopingEnabled; - size_t mLoopCount; + uint32_t mLoopCount; AnimPriority mPriority; int mBlendMask; @@ -379,7 +379,7 @@ namespace MWRender * the "start" and "stop" keys for looping? */ void play(std::string_view groupname, const AnimPriority& priority, int blendMask, bool autodisable, - float speedmult, std::string_view start, std::string_view stop, float startpoint, size_t loops, + float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback = false); /** Adjust the speed multiplier of an already playing animation. diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 8d439ec82b..16c1f5a134 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -56,7 +56,7 @@ namespace MWScript } MWBase::Environment::get().getMechanicsManager()->playAnimationGroup( - ptr, group, mode, std::numeric_limits::max(), true); + ptr, group, mode, std::numeric_limits::max(), true); } }; From 993cea7d657db6b16eb65a08f1870ba394c87858 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 28 Jan 2024 16:24:15 +0100 Subject: [PATCH 20/26] MR Comments --- apps/openmw/mwmechanics/character.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 08a476631c..2b980ebd41 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2516,7 +2516,8 @@ namespace MWMechanics { AnimationQueueEntry entry; entry.mGroup = iter->mGroup; - entry.mLoopCount = iter->mLoopCount; + entry.mLoopCount + = static_cast(std::min(iter->mLoopCount, std::numeric_limits::max())); entry.mLooping = mAnimation->isLoopingAnimation(entry.mGroup); entry.mStartKey = "start"; entry.mStopKey = "stop"; @@ -2586,7 +2587,6 @@ namespace MWMechanics // exactly x times, while non-actors will loop x+1 instead. if (mPtr.getClass().isActor()) count--; - count = std::max(count, 0u); AnimationQueueEntry entry; entry.mGroup = groupname; @@ -2633,7 +2633,7 @@ namespace MWMechanics entry.mGroup = groupname; // Note: MWScript gives one less loop to actors than non-actors. // But this is the Lua version. We don't need to reproduce this weirdness here. - entry.mLoopCount = std::max(loops, 0u); + entry.mLoopCount = loops; entry.mStartKey = startKey; entry.mStopKey = stopKey; entry.mLooping = mAnimation->isLoopingAnimation(groupname) || forceLoop; From 24a0a0c3bf8c311992187d4f95841a4cc0c928fb Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 28 Jan 2024 16:34:44 +0100 Subject: [PATCH 21/26] size_t -> uint32_t --- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2b980ebd41..5c01c5c355 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2540,7 +2540,7 @@ namespace MWMechanics void CharacterController::playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, - float startpoint, size_t loops, bool loopfallback) const + float startpoint, uint32_t loops, bool loopfallback) const { if (mLuaAnimations) MWBase::Environment::get().getLuaManager()->playAnimation(mPtr, groupname, priority, blendMask, autodisable, diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 7146baeb0e..430635fff5 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -276,7 +276,7 @@ namespace MWMechanics void playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, - size_t loops, bool loopfallback = false) const; + uint32_t loops, bool loopfallback = false) const; bool playGroup(std::string_view groupname, int mode, uint32_t count, bool scripted = false); bool playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey, uint32_t loops, bool forceLoop); From 0ed94ead4ea257a6b7e826e6117ca9bc06928ebb Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 28 Jan 2024 17:34:22 +0100 Subject: [PATCH 22/26] Check that count is non-zero before decrementing it. --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5c01c5c355..8d69da2c43 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2585,7 +2585,7 @@ namespace MWMechanics // if played with a count of 0, all objects play exactly once from start to stop. // But if the count is x > 0, actors and non-actors behave differently. actors will loop // exactly x times, while non-actors will loop x+1 instead. - if (mPtr.getClass().isActor()) + if (mPtr.getClass().isActor() && count > 0) count--; AnimationQueueEntry entry; From fbffecfd13ed858feb1294ae28bbc2d6aa0d78c6 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 28 Jan 2024 21:02:06 +0100 Subject: [PATCH 23/26] ~0ul -> std::numeric_limits::max() --- apps/openmw/mwrender/characterpreview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index aa6b5eb4dd..59844ee9ae 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -464,7 +464,7 @@ namespace MWRender { if (!mAnimation->getInfo("torch")) mAnimation->play( - "torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); + "torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, std::numeric_limits::max(), true); } else if (mAnimation->getInfo("torch")) mAnimation->disable("torch"); From c90ebcc86b948780f0b26e4883c2c37e72b5d2d6 Mon Sep 17 00:00:00 2001 From: Yury Stepovikov Date: Sun, 28 Jan 2024 21:33:10 +0000 Subject: [PATCH 24/26] Allow multiselect in the archives tab (#7606) --- CHANGELOG.md | 1 + apps/launcher/datafilespage.cpp | 80 ++++++- apps/launcher/datafilespage.hpp | 11 +- apps/launcher/ui/datafilespage.ui | 333 ++++++++++++++++-------------- 4 files changed, 261 insertions(+), 164 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b014ca0389..0b49b0ae9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,6 +166,7 @@ Feature #7546: Start the game on Fredas Feature #7554: Controller binding for tab for menu navigation Feature #7568: Uninterruptable scripted music + Feature #7606: Launcher: allow Shift-select in Archives tab Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7618: Show the player character's health in the save details Feature #7625: Add some missing console error outputs diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 114221ce92..70ebe29625 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -3,7 +3,9 @@ #include #include +#include #include +#include #include #include @@ -162,8 +164,8 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); }); connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); }); connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); }); - connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchive(-1); }); - connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchive(1); }); + connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); }); + connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); }); connect( ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); }); @@ -218,6 +220,18 @@ void Launcher::DataFilesPage::buildView() &DataFilesPage::readNavMeshToolStderr); connect(mNavMeshToolInvoker->getProcess(), qOverload(&QProcess::finished), this, &DataFilesPage::navMeshToolFinished); + + buildArchiveContextMenu(); +} + +void Launcher::DataFilesPage::buildArchiveContextMenu() +{ + connect(ui.archiveListWidget, &QListWidget::customContextMenuRequested, this, + &DataFilesPage::slotShowArchiveContextMenu); + + mArchiveContextMenu = new QMenu(ui.archiveListWidget); + mArchiveContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems())); + mArchiveContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems())); } bool Launcher::DataFilesPage::loadSettings() @@ -707,17 +721,71 @@ void Launcher::DataFilesPage::removeDirectory() refreshDataFilesView(); } -void Launcher::DataFilesPage::moveArchive(int step) +void Launcher::DataFilesPage::slotShowArchiveContextMenu(const QPoint& pos) { - int selectedRow = ui.archiveListWidget->currentRow(); + QPoint globalPos = ui.archiveListWidget->viewport()->mapToGlobal(pos); + mArchiveContextMenu->exec(globalPos); +} + +void Launcher::DataFilesPage::setCheckStateForMultiSelectedItems(bool checked) +{ + Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked; + + for (QListWidgetItem* selectedItem : ui.archiveListWidget->selectedItems()) + { + selectedItem->setCheckState(checkState); + } +} + +void Launcher::DataFilesPage::slotUncheckMultiSelectedItems() +{ + setCheckStateForMultiSelectedItems(false); +} + +void Launcher::DataFilesPage::slotCheckMultiSelectedItems() +{ + setCheckStateForMultiSelectedItems(true); +} + +void Launcher::DataFilesPage::moveArchives(int step) +{ + QList selectedItems = ui.archiveListWidget->selectedItems(); + QList> sortedItems; + + for (QListWidgetItem* selectedItem : selectedItems) + { + int selectedRow = ui.archiveListWidget->row(selectedItem); + sortedItems.append(qMakePair(selectedRow, selectedItem)); + } + + if (step > 0) + { + std::sort(sortedItems.begin(), sortedItems.end(), [](auto a, auto b) { return a.first > b.first; }); + } + else + { + std::sort(sortedItems.begin(), sortedItems.end(), [](auto a, auto b) { return a.first < b.first; }); + } + + for (auto i : sortedItems) + { + if (!moveArchive(i.second, step)) + break; + } +} + +bool Launcher::DataFilesPage::moveArchive(QListWidgetItem* listItem, int step) +{ + int selectedRow = ui.archiveListWidget->row(listItem); int newRow = selectedRow + step; if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1) - return; + return false; - const auto* item = ui.archiveListWidget->takeItem(selectedRow); + const QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow); addArchive(item->text(), item->checkState(), newRow); ui.archiveListWidget->setCurrentRow(newRow); + return true; } void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row) diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index dc3aeaef6f..e568137e8f 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -39,6 +40,7 @@ namespace Launcher ContentSelectorView::ContentSelector* mSelector; Ui::DataFilesPage ui; + QMenu* mArchiveContextMenu; public: explicit DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings, @@ -72,9 +74,13 @@ namespace Launcher void addSubdirectories(bool append); void sortDirectories(); void removeDirectory(); - void moveArchive(int step); + void moveArchives(int step); void moveDirectory(int step); + void slotShowArchiveContextMenu(const QPoint& pos); + void slotCheckMultiSelectedItems(); + void slotUncheckMultiSelectedItems(); + void on_newProfileAction_triggered(); void on_cloneProfileAction_triggered(); void on_deleteProfileAction_triggered(); @@ -120,7 +126,10 @@ namespace Launcher void addArchive(const QString& name, Qt::CheckState selected, int row = -1); void addArchivesFromDir(const QString& dir); + bool moveArchive(QListWidgetItem* listItem, int step); void buildView(); + void buildArchiveContextMenu(); + void setCheckStateForMultiSelectedItems(bool checked); void setProfile(int index, bool savePrevious); void setProfile(const QString& previous, const QString& current, bool savePrevious); void removeProfile(const QString& profile); diff --git a/apps/launcher/ui/datafilespage.ui b/apps/launcher/ui/datafilespage.ui index 249207123e..2b54307838 100644 --- a/apps/launcher/ui/datafilespage.ui +++ b/apps/launcher/ui/datafilespage.ui @@ -7,7 +7,7 @@ 0 0 573 - 384 + 557 @@ -29,6 +29,12 @@ + + + 0 + 0 + + <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -41,14 +47,111 @@ Data Directories - + QAbstractItemView::InternalMove - + + + + + + + 0 + 33 + + + + Scan directories for likely data directories and append them at the end of the list. + + + Append + + + + + + + + 0 + 33 + + + + Scan directories for likely data directories and insert them above the selected position + + + Insert Above + + + + + + + + 0 + 33 + + + + Move selected directory one position up + + + Move Up + + + + + + + + 0 + 33 + + + + Move selected directory one position down + + + Move Down + + + + + + + + 0 + 33 + + + + Remove selected directory + + + Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + @@ -61,116 +164,6 @@ - - - - - 0 - 33 - - - - - 0 - 33 - - - - Scan directories for likely data directories and append them at the end of the list. - - - Append - - - - - - - - 0 - 33 - - - - - 0 - 33 - - - - Scan directories for likely data directories and insert them above the selected position - - - Insert Above - - - - - - - - 0 - 33 - - - - - 0 - 33 - - - - Move selected directory one position up - - - Move Up - - - - - - - - 0 - 33 - - - - - 0 - 33 - - - - Move selected directory one position down - - - Move Down - - - - - - - - 0 - 33 - - - - - 0 - 33 - - - - Remove selected directory - - - Remove - - - @@ -178,64 +171,90 @@ Archive Files - + + + Qt::CustomContextMenu + QAbstractItemView::InternalMove + + Qt::CopyAction + + + QAbstractItemView::ExtendedSelection + - - - - 0 - 33 - - - - - 0 - 33 - - - - Move selected archive one position up - - - Move Up - - + + + + + + 0 + 33 + + + + + 0 + 33 + + + + Move selected archive one position up + + + Move Up + + + + + + + + 0 + 33 + + + + + 0 + 33 + + + + Move selected archive one position down + + + Move Down + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + - + <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - - - - 0 - 33 - - - - - 0 - 33 - - - - Move selected archive one position down - - - Move Down - - - From f9b69623d35a23d7eb9a00487e2645a2585c842a Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 29 Jan 2024 11:01:52 +0100 Subject: [PATCH 25/26] Remove stateless encoder from ReadersCache It was added by https://gitlab.com/OpenMW/openmw/-/merge_requests/2804 without a good reason. There is already encoder available in the used context. --- apps/openmw/mwworld/esmloader.cpp | 11 ++++++----- apps/openmw/mwworld/worldimp.cpp | 2 -- components/esm3/readerscache.hpp | 14 +------------- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index e586a4c204..0be90c65f0 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -64,11 +64,12 @@ namespace MWWorld } case ESM::Format::Tes4: { - ESM4::Reader readerESM4(std::move(stream), filepath, - MWBase::Environment::get().getResourceSystem()->getVFS(), mReaders.getStatelessEncoder()); - readerESM4.setModIndex(index); - readerESM4.updateModIndices(mNameToIndex); - mStore.loadESM4(readerESM4); + ESM4::Reader reader(std::move(stream), filepath, + MWBase::Environment::get().getResourceSystem()->getVFS(), + mEncoder != nullptr ? &mEncoder->getStatelessEncoder() : nullptr); + reader.setModIndex(index); + reader.updateModIndices(mNameToIndex); + mStore.loadESM4(reader); break; } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c6282c6f5a..a265ff0a76 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -274,8 +274,6 @@ namespace MWWorld const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { mContentFiles = contentFiles; - if (encoder) - mReaders.setStatelessEncoder(encoder->getStatelessEncoder()); mESMVersions.resize(mContentFiles.size(), -1); loadContentFiles(fileCollections, contentFiles, encoder, listener); diff --git a/components/esm3/readerscache.hpp b/components/esm3/readerscache.hpp index 94bee206ec..5d8c2afcbd 100644 --- a/components/esm3/readerscache.hpp +++ b/components/esm3/readerscache.hpp @@ -9,8 +9,6 @@ #include #include -#include - namespace ESM { class ReadersCache @@ -57,23 +55,13 @@ namespace ESM BusyItem get(std::size_t index); - void setStatelessEncoder(const ToUTF8::StatelessUtf8Encoder& statelessEncoderPtr) - { - mStatelessEncoder.emplace(statelessEncoderPtr); - } - - const ToUTF8::StatelessUtf8Encoder* getStatelessEncoder() - { - return mStatelessEncoder.has_value() ? &mStatelessEncoder.value() : nullptr; - } - private: const std::size_t mCapacity; std::map::iterator> mIndex; std::list mBusyItems; std::list mFreeItems; std::list mClosedItems; - std::optional mStatelessEncoder; + inline void closeExtraReaders(); inline void releaseItem(std::list::iterator it) noexcept; From c7fcd1c31b644c67d44f75024f6845c549012ad2 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 31 Jan 2024 17:00:33 -0600 Subject: [PATCH 26/26] Fix formatting --- apps/openmw/mwrender/characterpreview.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 59844ee9ae..9914aec7ca 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -463,8 +463,8 @@ namespace MWRender if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && showCarriedLeft) { if (!mAnimation->getInfo("torch")) - mAnimation->play( - "torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, std::numeric_limits::max(), true); + mAnimation->play("torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, + std::numeric_limits::max(), true); } else if (mAnimation->getInfo("torch")) mAnimation->disable("torch");