diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d31fba12..8c1dc88d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Bug #4948: Footstep sounds while levitating on ground level Bug #4963: Enchant skill progress is incorrect Bug #4965: Global light attenuation settings setup is lacking + Bug #4969: "Miss" sound plays for any actor Feature #2229: Improve pathfinding AI Feature #3442: Default values for fallbacks from ini file Feature #3610: Option to invert X axis diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index f3b367087..305402b80 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -376,7 +376,8 @@ namespace MWClass if (!successful) { // Missed - MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f); + if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) + MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f); return; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 037f98954..19ad8c66b 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -710,7 +710,8 @@ namespace MWClass if (!successful) { // Missed - sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f); + if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) + sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f); return; } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index ef81565c2..01ce57581 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -126,8 +126,12 @@ namespace DetourNavigator try { if (auto job = getNextJob()) - if (!processJob(*job)) + { + const auto processed = processJob(*job); + unlockTile(job->mAgentHalfExtents, job->mChangedTile); + if (!processed) repost(std::move(*job)); + } } catch (const std::exception& e) { @@ -178,19 +182,47 @@ namespace DetourNavigator boost::optional AsyncNavMeshUpdater::getNextJob() { std::unique_lock lock(mMutex); - if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), [&] { return !mJobs.empty(); })) + + const auto threadId = std::this_thread::get_id(); + auto& threadQueue = mThreadsQueues[threadId]; + + while (true) { - mFirstStart.lock()->reset(); - mDone.notify_all(); - return boost::none; + const auto hasJob = [&] { return !mJobs.empty() || !threadQueue.mPushed.empty(); }; + + if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) + { + mFirstStart.lock()->reset(); + mDone.notify_all(); + return boost::none; + } + + Log(Debug::Debug) << "Got " << mJobs.size() << " navigator jobs and " + << threadQueue.mJobs.size() << " thread jobs"; + + auto job = threadQueue.mJobs.empty() + ? getJob(mJobs, mPushed) + : getJob(threadQueue.mJobs, threadQueue.mPushed); + + const auto owner = lockTile(job.mAgentHalfExtents, job.mChangedTile); + + if (owner == threadId) + return job; + + postThreadJob(std::move(job), mThreadsQueues[owner]); } - Log(Debug::Debug) << "Got " << mJobs.size() << " navigator jobs"; - const auto job = mJobs.top(); - mJobs.pop(); - const auto pushed = mPushed.find(job.mAgentHalfExtents); - pushed->second.erase(job.mChangedTile); - if (pushed->second.empty()) - mPushed.erase(pushed); + } + + AsyncNavMeshUpdater::Job AsyncNavMeshUpdater::getJob(Jobs& jobs, Pushed& pushed) + { + auto job = jobs.top(); + jobs.pop(); + + const auto it = pushed.find(job.mAgentHalfExtents); + it->second.erase(job.mChangedTile); + if (it->second.empty()) + pushed.erase(it); + return job; } @@ -239,4 +271,60 @@ namespace DetourNavigator mHasJob.notify_all(); } } + + void AsyncNavMeshUpdater::postThreadJob(Job&& job, Queue& queue) + { + if (queue.mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second) + { + queue.mJobs.push(std::move(job)); + mHasJob.notify_all(); + } + } + + std::thread::id AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) + { + if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) + return std::this_thread::get_id(); + + auto locked = mProcessingTiles.lock(); + + auto agent = locked->find(agentHalfExtents); + if (agent == locked->end()) + { + const auto threadId = std::this_thread::get_id(); + locked->emplace(agentHalfExtents, std::map({{changedTile, threadId}})); + return threadId; + } + + auto tile = agent->second.find(changedTile); + if (tile == agent->second.end()) + { + const auto threadId = std::this_thread::get_id(); + agent->second.emplace(changedTile, threadId); + return threadId; + } + + return tile->second; + } + + void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) + { + if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) + return; + + auto locked = mProcessingTiles.lock(); + + auto agent = locked->find(agentHalfExtents); + if (agent == locked->end()) + return; + + auto tile = agent->second.find(changedTile); + if (tile == agent->second.end()) + return; + + agent->second.erase(tile); + + if (agent->second.empty()) + locked->erase(agent); + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 3493ba02f..3f17d5ca0 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -69,6 +69,15 @@ namespace DetourNavigator }; using Jobs = std::priority_queue>; + using Pushed = std::map>; + + struct Queue + { + Jobs mJobs; + Pushed mPushed; + + Queue() = default; + }; std::reference_wrapper mSettings; std::reference_wrapper mRecastMeshManager; @@ -82,6 +91,8 @@ namespace DetourNavigator Misc::ScopeGuarded mPlayerTile; Misc::ScopeGuarded> mFirstStart; NavMeshTilesCache mNavMeshTilesCache; + Misc::ScopeGuarded>> mProcessingTiles; + std::map mThreadsQueues; std::vector mThreads; void process() throw(); @@ -90,11 +101,19 @@ namespace DetourNavigator boost::optional getNextJob(); + static Job getJob(Jobs& jobs, Pushed& pushed); + + void postThreadJob(Job&& job, Queue& queue); + void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; std::chrono::steady_clock::time_point setFirstStart(const std::chrono::steady_clock::time_point& value); void repost(Job&& job); + + std::thread::id lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); + + void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); }; } diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 0519e0cc6..6675fef08 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -119,19 +119,13 @@ static const std::map factories = makeFactory(); std::string NIFFile::printVersion(unsigned int version) { - union ver_quad - { - uint32_t full; - uint8_t quad[4]; - } version_out; - - version_out.full = version; + int major = (version >> 24) & 0xFF; + int minor = (version >> 16) & 0xFF; + int patch = (version >> 8) & 0xFF; + int rev = version & 0xFF; std::stringstream stream; - stream << version_out.quad[3] << "." - << version_out.quad[2] << "." - << version_out.quad[1] << "." - << version_out.quad[0]; + stream << major << "." << minor << "." << patch << "." << rev; return stream.str(); } @@ -146,7 +140,9 @@ void NIFFile::parse(Files::IStreamPtr stream) // Get BCD version ver = nif.getUInt(); - if(ver != VER_MW) + // 4.0.0.0 is an older, practically identical version of the format. + // It's not used by Morrowind assets but Morrowind supports it. + if(ver != 0x04000000 && ver != VER_MW) fail("Unsupported NIF version: " + printVersion(ver)); // Number of records size_t recNum = nif.getInt();