mirror of
https://github.com/OpenMW/openmw.git
synced 2025-10-17 00:46:34 +00:00
Merge branch openmw:master into master
This commit is contained in:
commit
3275b357ac
40 changed files with 849 additions and 592 deletions
|
@ -1,6 +1,7 @@
|
|||
Checks: >
|
||||
-*,
|
||||
portability-*,
|
||||
-portability-template-virtual-member-function,
|
||||
clang-analyzer-*,
|
||||
-clang-analyzer-optin.*,
|
||||
-clang-analyzer-cplusplus.NewDeleteLeaks,
|
||||
|
@ -16,4 +17,4 @@ CheckOptions:
|
|||
- key: readability-identifier-naming.NamespaceCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.NamespaceIgnoredRegexp
|
||||
value: 'osg(DB|FX|Particle|Shadow|Viewer|Util)?'
|
||||
value: 'bpo|osg(DB|FX|Particle|Shadow|Viewer|Util)?'
|
||||
|
|
|
@ -139,7 +139,7 @@ namespace
|
|||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty)
|
||||
{
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::NavMeshNotFound);
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>());
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ namespace
|
|||
TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception)
|
||||
{
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::StartPolygonNotFound);
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ namespace
|
|||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
mNavigator->removeAgent(mAgentBounds);
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::StartPolygonNotFound);
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ namespace
|
|||
updateGuard.reset();
|
||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -194,7 +194,7 @@ namespace
|
|||
updateGuard.reset();
|
||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mStart, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mStart, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath, ElementsAre(Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125))) << mPath;
|
||||
|
@ -218,7 +218,7 @@ namespace
|
|||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -237,7 +237,7 @@ namespace
|
|||
|
||||
mPath.clear();
|
||||
mOut = std::back_inserter(mPath);
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -265,7 +265,7 @@ namespace
|
|||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -285,7 +285,7 @@ namespace
|
|||
|
||||
mPath.clear();
|
||||
mOut = std::back_inserter(mPath);
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -318,7 +318,7 @@ namespace
|
|||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -386,7 +386,7 @@ namespace
|
|||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -421,7 +421,7 @@ namespace
|
|||
mEnd.x() = 256;
|
||||
mEnd.z() = 300;
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -453,8 +453,8 @@ namespace
|
|||
mStart.x() = 256;
|
||||
mEnd.x() = 256;
|
||||
|
||||
EXPECT_EQ(
|
||||
findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance,
|
||||
{}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -487,8 +487,8 @@ namespace
|
|||
mStart.x() = 256;
|
||||
mEnd.x() = 256;
|
||||
|
||||
EXPECT_EQ(
|
||||
findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance,
|
||||
{}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -520,7 +520,7 @@ namespace
|
|||
mStart.x() = 256;
|
||||
mEnd.x() = 256;
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -549,7 +549,7 @@ namespace
|
|||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -577,7 +577,7 @@ namespace
|
|||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -658,7 +658,7 @@ namespace
|
|||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -781,7 +781,7 @@ namespace
|
|||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -806,7 +806,7 @@ namespace
|
|||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::PartialPath);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -834,7 +834,7 @@ namespace
|
|||
|
||||
const float endTolerance = 1000.0f;
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, endTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, endTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
|
@ -979,6 +979,146 @@ namespace
|
|||
EXPECT_EQ(usedNavMeshTiles, 854);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_should_return_path_around_steep_mountains)
|
||||
{
|
||||
const std::array<float, 5 * 5> heightfieldData{ {
|
||||
0, 0, 0, 0, 0, // row 0
|
||||
0, 0, 0, 0, 0, // row 1
|
||||
0, 0, 1000, 0, 0, // row 2
|
||||
0, 0, 0, 0, 0, // row 3
|
||||
0, 0, 0, 0, 0, // row 4
|
||||
} };
|
||||
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
const osg::Vec3f start(56, 56, 12);
|
||||
const osg::Vec3f end(464, 464, 12);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
ElementsAre( //
|
||||
Vec3fEq(56.66664886474609375, 56.66664886474609375, 11.33333301544189453125),
|
||||
Vec3fEq(396.666656494140625, 79.33331298828125, 11.33333301544189453125),
|
||||
Vec3fEq(430.666656494140625, 113.33331298828125, 11.33333301544189453125),
|
||||
Vec3fEq(463.999969482421875, 463.999969482421875, 11.33333301544189453125)))
|
||||
<< mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_should_return_path_around_steep_cliffs)
|
||||
{
|
||||
const std::array<float, 5 * 5> heightfieldData{ {
|
||||
0, 0, 0, 0, 0, // row 0
|
||||
0, 0, 0, 0, 0, // row 1
|
||||
0, 0, -1000, 0, 0, // row 2
|
||||
0, 0, 0, 0, 0, // row 3
|
||||
0, 0, 0, 0, 0, // row 4
|
||||
} };
|
||||
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
const osg::Vec3f start(56, 56, 12);
|
||||
const osg::Vec3f end(464, 464, 12);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
ElementsAre( //
|
||||
Vec3fEq(56.66664886474609375, 56.66664886474609375, 8.66659259796142578125),
|
||||
Vec3fEq(385.33331298828125, 79.33331298828125, 8.66659259796142578125),
|
||||
Vec3fEq(430.666656494140625, 124.66664886474609375, 8.66659259796142578125),
|
||||
Vec3fEq(463.999969482421875, 463.999969482421875, 8.66659259796142578125)))
|
||||
<< mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_should_return_path_with_checkpoints)
|
||||
{
|
||||
const std::array<float, 5 * 5> heightfieldData{ {
|
||||
0, 0, 0, 0, 0, // row 0
|
||||
0, 0, 0, 0, 0, // row 1
|
||||
0, 0, 1000, 0, 0, // row 2
|
||||
0, 0, 0, 0, 0, // row 3
|
||||
0, 0, 0, 0, 0, // row 4
|
||||
} };
|
||||
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
const std::vector<osg::Vec3f> checkpoints = {
|
||||
osg::Vec3f(400, 70, 12),
|
||||
};
|
||||
|
||||
const osg::Vec3f start(56, 56, 12);
|
||||
const osg::Vec3f end(464, 464, 12);
|
||||
|
||||
EXPECT_EQ(
|
||||
findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, checkpoints, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
ElementsAre( //
|
||||
Vec3fEq(56.66664886474609375, 56.66664886474609375, 11.33333301544189453125),
|
||||
Vec3fEq(400, 70, 11.33333301544189453125),
|
||||
Vec3fEq(430.666656494140625, 113.33331298828125, 11.33333301544189453125),
|
||||
Vec3fEq(463.999969482421875, 463.999969482421875, 11.33333301544189453125)))
|
||||
<< mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_should_skip_unreachable_checkpoints)
|
||||
{
|
||||
const std::array<float, 5 * 5> heightfieldData{ {
|
||||
0, 0, 0, 0, 0, // row 0
|
||||
0, 0, 0, 0, 0, // row 1
|
||||
0, 0, 1000, 0, 0, // row 2
|
||||
0, 0, 0, 0, 0, // row 3
|
||||
0, 0, 0, 0, 0, // row 4
|
||||
} };
|
||||
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
const std::vector<osg::Vec3f> checkpoints = {
|
||||
osg::Vec3f(400, 70, 10000),
|
||||
osg::Vec3f(256, 256, 1000),
|
||||
osg::Vec3f(-1000, -1000, 0),
|
||||
};
|
||||
|
||||
const osg::Vec3f start(56, 56, 12);
|
||||
const osg::Vec3f end(464, 464, 12);
|
||||
|
||||
EXPECT_EQ(
|
||||
findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, checkpoints, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
ElementsAre( //
|
||||
Vec3fEq(56.66664886474609375, 56.66664886474609375, 11.33333301544189453125),
|
||||
Vec3fEq(396.666656494140625, 79.33331298828125, 11.33333301544189453125),
|
||||
Vec3fEq(430.666656494140625, 113.33331298828125, 11.33333301544189453125),
|
||||
Vec3fEq(463.999969482421875, 463.999969482421875, 11.33333301544189453125)))
|
||||
<< mPath;
|
||||
}
|
||||
|
||||
struct DetourNavigatorNavigatorNotSupportedAgentBoundsTest : TestWithParam<AgentBounds>
|
||||
{
|
||||
};
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace sfs = std::filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
// from configfileparser.cpp
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include <components/files/conversion.hpp>
|
||||
|
||||
namespace bpo = boost::program_options;
|
||||
namespace sfs = std::filesystem;
|
||||
|
||||
#ifndef _WIN32
|
||||
int main(int argc, char* argv[])
|
||||
|
|
|
@ -589,8 +589,7 @@ namespace MWBase
|
|||
virtual bool hasCollisionWithDoor(
|
||||
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0;
|
||||
|
||||
virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors = nullptr) const = 0;
|
||||
virtual bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const = 0;
|
||||
|
||||
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
|
||||
|
||||
|
|
|
@ -233,6 +233,7 @@ namespace MWLua
|
|||
DetourNavigator::Flags includeFlags = defaultIncludeFlags;
|
||||
DetourNavigator::AreaCosts areaCosts{};
|
||||
float destinationTolerance = 1;
|
||||
std::vector<osg::Vec3f> checkpoints;
|
||||
|
||||
if (options.has_value())
|
||||
{
|
||||
|
@ -258,13 +259,24 @@ namespace MWLua
|
|||
}
|
||||
if (const auto& v = options->get<sol::optional<float>>("destinationTolerance"))
|
||||
destinationTolerance = *v;
|
||||
if (const auto& t = options->get<sol::optional<sol::table>>("checkpoints"))
|
||||
{
|
||||
for (const auto& [k, v] : *t)
|
||||
{
|
||||
const int index = k.as<int>();
|
||||
const osg::Vec3f position = v.as<osg::Vec3f>();
|
||||
if (index != static_cast<int>(checkpoints.size() + 1))
|
||||
throw std::runtime_error("checkpoints is not an array");
|
||||
checkpoints.push_back(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<osg::Vec3f> path;
|
||||
|
||||
const DetourNavigator::Status status
|
||||
= DetourNavigator::findPath(*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds,
|
||||
source, destination, includeFlags, areaCosts, destinationTolerance, std::back_inserter(path));
|
||||
const DetourNavigator::Status status = DetourNavigator::findPath(
|
||||
*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, source, destination,
|
||||
includeFlags, areaCosts, destinationTolerance, checkpoints, std::back_inserter(path));
|
||||
|
||||
sol::table result(lua, sol::create);
|
||||
LuaUtil::copyVectorToTable(path, result);
|
||||
|
|
|
@ -73,6 +73,21 @@ namespace
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
struct ActiveSpells::UpdateContext
|
||||
{
|
||||
bool mUpdatedEnemy = false;
|
||||
bool mUpdatedHitOverlay = false;
|
||||
bool mUpdateSpellWindow = false;
|
||||
bool mPlayNonLooping = false;
|
||||
bool mEraseRemoved = false;
|
||||
bool mUpdate;
|
||||
|
||||
UpdateContext(bool update)
|
||||
: mUpdate(update)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells)
|
||||
: mActiveSpells(spells)
|
||||
{
|
||||
|
@ -256,8 +271,9 @@ namespace MWMechanics
|
|||
++spellIt;
|
||||
}
|
||||
|
||||
UpdateContext context(duration > 0.f);
|
||||
for (const auto& spell : mQueue)
|
||||
addToSpells(ptr, spell);
|
||||
addToSpells(ptr, spell, context);
|
||||
mQueue.clear();
|
||||
|
||||
// Vanilla only does this on cell change I think
|
||||
|
@ -267,20 +283,17 @@ namespace MWMechanics
|
|||
if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power
|
||||
&& !isSpellActive(spell->mId))
|
||||
{
|
||||
mSpells.emplace_back(ActiveSpellParams{ spell, ptr, true });
|
||||
mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
||||
initParams(ptr, ActiveSpellParams{ spell, ptr, true }, context);
|
||||
}
|
||||
}
|
||||
|
||||
bool updateSpellWindow = false;
|
||||
bool playNonLooping = false;
|
||||
if (ptr.getClass().hasInventoryStore(ptr)
|
||||
&& !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()))
|
||||
{
|
||||
auto& store = ptr.getClass().getInventoryStore(ptr);
|
||||
if (store.getInvListener() != nullptr)
|
||||
{
|
||||
playNonLooping = !store.isFirstEquip();
|
||||
context.mPlayNonLooping = !store.isFirstEquip();
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++)
|
||||
{
|
||||
|
@ -306,82 +319,108 @@ namespace MWMechanics
|
|||
// invisibility manually
|
||||
purgeEffect(ptr, ESM::MagicEffect::Invisibility);
|
||||
applyPurges(ptr);
|
||||
ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr });
|
||||
params.setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
||||
updateSpellWindow = true;
|
||||
ActiveSpellParams* params = initParams(ptr, ActiveSpellParams{ *slot, enchantment, ptr }, context);
|
||||
if (params)
|
||||
context.mUpdateSpellWindow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||
bool updatedHitOverlay = false;
|
||||
bool updatedEnemy = false;
|
||||
// Update effects
|
||||
context.mEraseRemoved = true;
|
||||
for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
|
||||
{
|
||||
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(
|
||||
spellIt->mCasterActorId); // Maybe make this search outside active grid?
|
||||
bool removedSpell = false;
|
||||
std::optional<ActiveSpellParams> reflected;
|
||||
for (auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
|
||||
{
|
||||
auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration, playNonLooping);
|
||||
if (result.mType == MagicApplicationResult::Type::REFLECTED)
|
||||
{
|
||||
if (!reflected)
|
||||
{
|
||||
if (Settings::game().mClassicReflectedAbsorbSpellsBehavior)
|
||||
reflected = { *spellIt, caster };
|
||||
else
|
||||
reflected = { *spellIt, ptr };
|
||||
}
|
||||
auto& reflectedEffect = reflected->mEffects.emplace_back(*it);
|
||||
reflectedEffect.mFlags
|
||||
= ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
|
||||
it = spellIt->mEffects.erase(it);
|
||||
}
|
||||
else if (result.mType == MagicApplicationResult::Type::REMOVED)
|
||||
it = spellIt->mEffects.erase(it);
|
||||
else
|
||||
{
|
||||
++it;
|
||||
if (!updatedEnemy && result.mShowHealth && caster == player && ptr != player)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(ptr);
|
||||
updatedEnemy = true;
|
||||
}
|
||||
if (!updatedHitOverlay && result.mShowHit && ptr == player)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
||||
updatedHitOverlay = true;
|
||||
}
|
||||
}
|
||||
removedSpell = applyPurges(ptr, &spellIt, &it);
|
||||
if (removedSpell)
|
||||
break;
|
||||
}
|
||||
if (reflected)
|
||||
{
|
||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getESMStore()->get<ESM::Static>().find(
|
||||
ESM::RefId::stringRefId("VFX_Reflect"));
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
|
||||
if (animation && !reflectStatic->mModel.empty())
|
||||
{
|
||||
const VFS::Path::Normalized reflectStaticModel
|
||||
= Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(reflectStatic->mModel));
|
||||
animation->addEffect(
|
||||
reflectStaticModel, ESM::MagicEffect::indexToName(ESM::MagicEffect::Reflect), false);
|
||||
}
|
||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected);
|
||||
}
|
||||
if (removedSpell)
|
||||
continue;
|
||||
updateActiveSpell(ptr, duration, spellIt, context);
|
||||
}
|
||||
|
||||
if (Settings::game().mClassicCalmSpellsBehavior)
|
||||
{
|
||||
ESM::MagicEffect::Effects effect
|
||||
= ptr.getClass().isNpc() ? ESM::MagicEffect::CalmHumanoid : ESM::MagicEffect::CalmCreature;
|
||||
if (creatureStats.getMagicEffects().getOrDefault(effect).getMagnitude() > 0.f)
|
||||
creatureStats.getAiSequence().stopCombat();
|
||||
}
|
||||
|
||||
if (ptr == player && context.mUpdateSpellWindow)
|
||||
{
|
||||
// Something happened with the spell list -- possibly while the game is paused,
|
||||
// so we want to make the spell window get the memo.
|
||||
// We don't normally want to do this, so this targets constant enchantments.
|
||||
MWBase::Environment::get().getWindowManager()->updateSpellWindow();
|
||||
}
|
||||
}
|
||||
|
||||
bool ActiveSpells::updateActiveSpell(
|
||||
const MWWorld::Ptr& ptr, float duration, Collection::iterator& spellIt, UpdateContext& context)
|
||||
{
|
||||
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(
|
||||
spellIt->mCasterActorId); // Maybe make this search outside active grid?
|
||||
bool removedSpell = false;
|
||||
std::optional<ActiveSpellParams> reflected;
|
||||
for (auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
|
||||
{
|
||||
auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration, context.mPlayNonLooping);
|
||||
if (result.mType == MagicApplicationResult::Type::REFLECTED)
|
||||
{
|
||||
if (!reflected)
|
||||
{
|
||||
if (Settings::game().mClassicReflectedAbsorbSpellsBehavior)
|
||||
reflected = { *spellIt, caster };
|
||||
else
|
||||
reflected = { *spellIt, ptr };
|
||||
}
|
||||
auto& reflectedEffect = reflected->mEffects.emplace_back(*it);
|
||||
reflectedEffect.mFlags
|
||||
= ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
|
||||
it = spellIt->mEffects.erase(it);
|
||||
}
|
||||
else if (result.mType == MagicApplicationResult::Type::REMOVED)
|
||||
it = spellIt->mEffects.erase(it);
|
||||
else
|
||||
{
|
||||
const MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||
++it;
|
||||
if (!context.mUpdatedEnemy && result.mShowHealth && caster == player && ptr != player)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(ptr);
|
||||
context.mUpdatedEnemy = true;
|
||||
}
|
||||
if (!context.mUpdatedHitOverlay && result.mShowHit && ptr == player)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
||||
context.mUpdatedHitOverlay = true;
|
||||
}
|
||||
}
|
||||
removedSpell = applyPurges(ptr, &spellIt, &it);
|
||||
if (removedSpell)
|
||||
break;
|
||||
}
|
||||
if (reflected)
|
||||
{
|
||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getESMStore()->get<ESM::Static>().find(
|
||||
ESM::RefId::stringRefId("VFX_Reflect"));
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
|
||||
if (animation && !reflectStatic->mModel.empty())
|
||||
{
|
||||
const VFS::Path::Normalized reflectStaticModel
|
||||
= Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(reflectStatic->mModel));
|
||||
animation->addEffect(
|
||||
reflectStaticModel, ESM::MagicEffect::indexToName(ESM::MagicEffect::Reflect), false);
|
||||
}
|
||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected);
|
||||
}
|
||||
if (removedSpell)
|
||||
return true;
|
||||
|
||||
if (context.mEraseRemoved)
|
||||
{
|
||||
bool remove = false;
|
||||
if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore))
|
||||
{
|
||||
try
|
||||
{
|
||||
auto& spells = ptr.getClass().getCreatureStats(ptr).getSpells();
|
||||
remove = !spells.hasSpell(spellIt->mSourceSpellId);
|
||||
}
|
||||
catch (const std::runtime_error& e)
|
||||
|
@ -413,30 +452,27 @@ namespace MWMechanics
|
|||
for (const auto& effect : params.mEffects)
|
||||
onMagicEffectRemoved(ptr, params, effect);
|
||||
applyPurges(ptr, &spellIt);
|
||||
updateSpellWindow = true;
|
||||
continue;
|
||||
context.mUpdateSpellWindow = true;
|
||||
return true;
|
||||
}
|
||||
++spellIt;
|
||||
}
|
||||
|
||||
if (Settings::game().mClassicCalmSpellsBehavior)
|
||||
{
|
||||
ESM::MagicEffect::Effects effect
|
||||
= ptr.getClass().isNpc() ? ESM::MagicEffect::CalmHumanoid : ESM::MagicEffect::CalmCreature;
|
||||
if (creatureStats.getMagicEffects().getOrDefault(effect).getMagnitude() > 0.f)
|
||||
creatureStats.getAiSequence().stopCombat();
|
||||
}
|
||||
|
||||
if (ptr == player && updateSpellWindow)
|
||||
{
|
||||
// Something happened with the spell list -- possibly while the game is paused,
|
||||
// so we want to make the spell window get the memo.
|
||||
// We don't normally want to do this, so this targets constant enchantments.
|
||||
MWBase::Environment::get().getWindowManager()->updateSpellWindow();
|
||||
}
|
||||
++spellIt;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell)
|
||||
ActiveSpells::ActiveSpellParams* ActiveSpells::initParams(
|
||||
const MWWorld::Ptr& ptr, const ActiveSpellParams& params, UpdateContext& context)
|
||||
{
|
||||
mSpells.emplace_back(params).setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
||||
auto it = mSpells.end();
|
||||
--it;
|
||||
// We instantly apply the effect with a duration of 0 so continuous effects can be purged before truly applying
|
||||
if (context.mUpdate && updateActiveSpell(ptr, 0.f, it, context))
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
|
||||
void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell, UpdateContext& context)
|
||||
{
|
||||
if (!spell.hasFlag(ESM::ActiveSpells::Flag_Stackable))
|
||||
{
|
||||
|
@ -454,8 +490,7 @@ namespace MWMechanics
|
|||
onMagicEffectRemoved(ptr, params, effect);
|
||||
}
|
||||
}
|
||||
mSpells.emplace_back(spell);
|
||||
mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
||||
initParams(ptr, spell, context);
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpells()
|
||||
|
@ -608,6 +643,8 @@ namespace MWMechanics
|
|||
{
|
||||
purge(
|
||||
[=](const ActiveSpellParams&, const ESM::ActiveEffect& effect) {
|
||||
if (!(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||
return false;
|
||||
if (effectArg.empty())
|
||||
return effect.mEffectId == effectId;
|
||||
return effect.mEffectId == effectId && effect.getSkillOrAttribute() == effectArg;
|
||||
|
|
|
@ -116,17 +116,23 @@ namespace MWMechanics
|
|||
IterationGuard(ActiveSpells& spells);
|
||||
~IterationGuard();
|
||||
};
|
||||
struct UpdateContext;
|
||||
|
||||
std::list<ActiveSpellParams> mSpells;
|
||||
std::vector<ActiveSpellParams> mQueue;
|
||||
std::queue<Predicate> mPurges;
|
||||
bool mIterating;
|
||||
|
||||
void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell);
|
||||
void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell, UpdateContext& context);
|
||||
|
||||
bool applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell = nullptr,
|
||||
std::vector<ActiveEffect>::iterator* currentEffect = nullptr);
|
||||
|
||||
bool updateActiveSpell(
|
||||
const MWWorld::Ptr& ptr, float duration, Collection::iterator& spellIt, UpdateContext& context);
|
||||
|
||||
ActiveSpellParams* initParams(const MWWorld::Ptr& ptr, const ActiveSpellParams& params, UpdateContext& context);
|
||||
|
||||
public:
|
||||
ActiveSpells();
|
||||
|
||||
|
|
|
@ -1326,19 +1326,37 @@ namespace MWMechanics
|
|||
|
||||
const MWWorld::Ptr player = getPlayer();
|
||||
const MWBase::World* const world = MWBase::Environment::get().getWorld();
|
||||
|
||||
struct CacheEntry
|
||||
{
|
||||
MWWorld::Ptr mPtr;
|
||||
float mMaxSpeed;
|
||||
osg::Vec3f mHalfExtents;
|
||||
Movement& mMovement;
|
||||
};
|
||||
|
||||
std::vector<CacheEntry> cache;
|
||||
cache.reserve(mActors.size());
|
||||
for (const Actor& actor : mActors)
|
||||
{
|
||||
if (actor.isInvalid())
|
||||
continue;
|
||||
const MWWorld::Ptr& ptr = actor.getPtr();
|
||||
const MWWorld::Class& cls = ptr.getClass();
|
||||
cache.push_back({ ptr, cls.getMaxSpeed(ptr), world->getHalfExtents(ptr), cls.getMovementSettings(ptr) });
|
||||
}
|
||||
|
||||
for (const CacheEntry& cached : cache)
|
||||
{
|
||||
const MWWorld::Ptr& ptr = cached.mPtr;
|
||||
if (ptr == player)
|
||||
continue; // Don't interfere with player controls.
|
||||
|
||||
const float maxSpeed = ptr.getClass().getMaxSpeed(ptr);
|
||||
const float maxSpeed = cached.mMaxSpeed;
|
||||
if (maxSpeed == 0.0)
|
||||
continue; // Can't move, so there is no sense to predict collisions.
|
||||
|
||||
Movement& movement = ptr.getClass().getMovementSettings(ptr);
|
||||
Movement& movement = cached.mMovement;
|
||||
const osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]);
|
||||
const bool isMoving = origMovement.length2() > 0.01;
|
||||
if (movement.mPosition[1] < 0)
|
||||
|
@ -1379,7 +1397,7 @@ namespace MWMechanics
|
|||
const osg::Vec2f baseSpeed = origMovement * maxSpeed;
|
||||
const osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3();
|
||||
const float baseRotZ = ptr.getRefData().getPosition().rot[2];
|
||||
const osg::Vec3f halfExtents = world->getHalfExtents(ptr);
|
||||
const osg::Vec3f& halfExtents = cached.mHalfExtents;
|
||||
const float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding;
|
||||
|
||||
float timeToCheck = maxTimeToCheck;
|
||||
|
@ -1392,15 +1410,13 @@ namespace MWMechanics
|
|||
float angleToApproachingActor = 0;
|
||||
|
||||
// Iterate through all other actors and predict collisions.
|
||||
for (const Actor& otherActor : mActors)
|
||||
for (const CacheEntry& otherCached : cache)
|
||||
{
|
||||
if (otherActor.isInvalid())
|
||||
continue;
|
||||
const MWWorld::Ptr& otherPtr = otherActor.getPtr();
|
||||
const MWWorld::Ptr& otherPtr = otherCached.mPtr;
|
||||
if (otherPtr == ptr || otherPtr == currentTarget)
|
||||
continue;
|
||||
|
||||
const osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr);
|
||||
const osg::Vec3f& otherHalfExtents = otherCached.mHalfExtents;
|
||||
const osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos;
|
||||
const osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ);
|
||||
const float dist = deltaPos.length();
|
||||
|
@ -1413,8 +1429,7 @@ namespace MWMechanics
|
|||
if (deltaPos.z() > halfExtents.z() * 2 || deltaPos.z() < -otherHalfExtents.z() * 2)
|
||||
continue;
|
||||
|
||||
const osg::Vec3f speed = otherPtr.getClass().getMovementSettings(otherPtr).asVec3()
|
||||
* otherPtr.getClass().getMaxSpeed(otherPtr);
|
||||
const osg::Vec3f speed = otherCached.mMovement.asVec3() * otherCached.mMaxSpeed;
|
||||
const float rotZ = otherPtr.getRefData().getPosition().rot[2];
|
||||
const osg::Vec2f relSpeed
|
||||
= Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed;
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
#include "aicombat.hpp"
|
||||
|
||||
#include <components/misc/coordinateconverter.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include <components/esm3/aisequence.hpp>
|
||||
|
||||
#include <components/misc/mathutil.hpp>
|
||||
|
||||
#include <components/detournavigator/navigatorutils.hpp>
|
||||
#include <components/esm3/aisequence.hpp>
|
||||
#include <components/misc/coordinateconverter.hpp>
|
||||
#include <components/misc/mathutil.hpp>
|
||||
#include <components/misc/pathgridutils.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
|
||||
#include "../mwphysics/raycasting.hpp"
|
||||
|
@ -178,11 +176,16 @@ namespace MWMechanics
|
|||
currentCell = actor.getCell();
|
||||
}
|
||||
|
||||
const MWWorld::Class& actorClass = actor.getClass();
|
||||
MWMechanics::CreatureStats& stats = actorClass.getCreatureStats(actor);
|
||||
if (stats.isParalyzed() || stats.getKnockedDown())
|
||||
return false;
|
||||
|
||||
bool forceFlee = false;
|
||||
if (!canFight(actor, target))
|
||||
{
|
||||
storage.stopAttack();
|
||||
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
|
||||
stats.setAttackingOrSpell(false);
|
||||
storage.mActionCooldown = 0.f;
|
||||
// Continue combat if target is player or player follower/escorter and an attack has been attempted
|
||||
const auto& playerFollowersAndEscorters
|
||||
|
@ -191,18 +194,14 @@ namespace MWMechanics
|
|||
= (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target)
|
||||
!= playerFollowersAndEscorters.end());
|
||||
if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer)
|
||||
&& ((actor.getClass().getCreatureStats(actor).getHitAttemptActorId()
|
||||
== target.getClass().getCreatureStats(target).getActorId())
|
||||
|| (target.getClass().getCreatureStats(target).getHitAttemptActorId()
|
||||
== actor.getClass().getCreatureStats(actor).getActorId())))
|
||||
&& ((stats.getHitAttemptActorId() == target.getClass().getCreatureStats(target).getActorId())
|
||||
|| (target.getClass().getCreatureStats(target).getHitAttemptActorId() == stats.getActorId())))
|
||||
forceFlee = true;
|
||||
else // Otherwise end combat
|
||||
return true;
|
||||
}
|
||||
|
||||
const MWWorld::Class& actorClass = actor.getClass();
|
||||
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||
|
||||
stats.setMovementFlag(CreatureStats::Flag_Run, true);
|
||||
float& actionCooldown = storage.mActionCooldown;
|
||||
std::unique_ptr<Action>& currentAction = storage.mCurrentAction;
|
||||
|
||||
|
@ -302,8 +301,8 @@ namespace MWMechanics
|
|||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||
const auto& pathGridGraph = getPathGridGraph(pathgrid);
|
||||
mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, agentBounds,
|
||||
navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full);
|
||||
mPathFinder.buildPath(actor, vActorPos, vTargetPos, pathGridGraph, agentBounds, navigatorFlags, areaCosts,
|
||||
storage.mAttackRange, PathType::Full);
|
||||
|
||||
if (!mPathFinder.isPathConstructed())
|
||||
{
|
||||
|
@ -316,8 +315,8 @@ namespace MWMechanics
|
|||
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
|
||||
{
|
||||
// If the point is close enough, try to find a path to that point.
|
||||
mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, agentBounds,
|
||||
navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full);
|
||||
mPathFinder.buildPath(actor, vActorPos, *hit, pathGridGraph, agentBounds, navigatorFlags, areaCosts,
|
||||
storage.mAttackRange, PathType::Full);
|
||||
if (mPathFinder.isPathConstructed())
|
||||
{
|
||||
// If path to that point is found use it as custom destination.
|
||||
|
@ -330,7 +329,7 @@ namespace MWMechanics
|
|||
{
|
||||
storage.mUseCustomDestination = false;
|
||||
storage.stopAttack();
|
||||
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
|
||||
stats.setAttackingOrSpell(false);
|
||||
currentAction = std::make_unique<ActionFlee>();
|
||||
actionCooldown = currentAction->getActionCooldown();
|
||||
storage.startFleeing();
|
||||
|
@ -393,8 +392,8 @@ namespace MWMechanics
|
|||
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
||||
coords.toLocal(localPos);
|
||||
|
||||
size_t closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos);
|
||||
for (size_t i = 0; i < pathgrid->mPoints.size(); i++)
|
||||
const std::size_t closestPointIndex = Misc::getClosestPoint(*pathgrid, localPos);
|
||||
for (std::size_t i = 0; i < pathgrid->mPoints.size(); i++)
|
||||
{
|
||||
if (i != closestPointIndex
|
||||
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, i))
|
||||
|
@ -455,7 +454,8 @@ namespace MWMechanics
|
|||
float dist
|
||||
= (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
||||
if ((dist > fFleeDistance && !storage.mLOS)
|
||||
|| pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration, supportedMovementDirections))
|
||||
|| pathTo(
|
||||
actor, Misc::Convert::makeOsgVec3f(storage.mFleeDest), duration, supportedMovementDirections))
|
||||
{
|
||||
state = AiCombatStorage::FleeState_Idle;
|
||||
}
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
#define GAME_MWMECHANICS_AICOMBAT_H
|
||||
|
||||
#include "aitemporarybase.hpp"
|
||||
#include "aitimer.hpp"
|
||||
#include "movement.hpp"
|
||||
#include "typedaipackage.hpp"
|
||||
|
||||
#include "../mwworld/cellstore.hpp" // for Doors
|
||||
|
||||
#include "aitimer.hpp"
|
||||
#include "movement.hpp"
|
||||
#include <components/esm3/loadpgrd.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
|
|
@ -180,8 +180,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
|||
= world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||
mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(pathgrid),
|
||||
agentBounds, navigatorFlags, areaCosts, endTolerance, pathType);
|
||||
mPathFinder.buildLimitedPath(actor, position, dest, getPathGridGraph(pathgrid), agentBounds,
|
||||
navigatorFlags, areaCosts, endTolerance, pathType);
|
||||
mRotateOnTheRunChecks = 3;
|
||||
|
||||
// give priority to go directly on target if there is minimal opportunity
|
||||
|
@ -501,7 +501,11 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::
|
|||
result |= DetourNavigator::Flag_swim;
|
||||
|
||||
if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0)
|
||||
result |= DetourNavigator::Flag_walk | DetourNavigator::Flag_usePathgrid;
|
||||
{
|
||||
result |= DetourNavigator::Flag_walk;
|
||||
if (getTypeId() != AiPackageTypeId::Wander)
|
||||
result |= DetourNavigator::Flag_usePathgrid;
|
||||
}
|
||||
|
||||
if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander)
|
||||
result |= DetourNavigator::Flag_openDoor;
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
namespace ESM
|
||||
{
|
||||
struct Cell;
|
||||
struct Pathgrid;
|
||||
|
||||
namespace AiSequence
|
||||
{
|
||||
struct AiSequence;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <components/detournavigator/navigatorutils.hpp>
|
||||
#include <components/esm3/aisequence.hpp>
|
||||
#include <components/misc/coordinateconverter.hpp>
|
||||
#include <components/misc/pathgridutils.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
@ -29,17 +30,6 @@
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
static const int COUNT_BEFORE_RESET = 10;
|
||||
static const float IDLE_POSITION_CHECK_INTERVAL = 1.5f;
|
||||
|
||||
// to prevent overcrowding
|
||||
static const int DESTINATION_TOLERANCE = 64;
|
||||
|
||||
// distance must be long enough that NPC will need to move to get there.
|
||||
static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2;
|
||||
|
||||
static const std::size_t MAX_IDLE_SIZE = 8;
|
||||
|
||||
const std::string_view AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
|
||||
"idle2",
|
||||
"idle3",
|
||||
|
@ -53,11 +43,22 @@ namespace MWMechanics
|
|||
|
||||
namespace
|
||||
{
|
||||
constexpr int countBeforeReset = 10;
|
||||
constexpr float idlePositionCheckInterval = 1.5f;
|
||||
|
||||
// to prevent overcrowding
|
||||
constexpr unsigned destinationTolerance = 64;
|
||||
|
||||
// distance must be long enough that NPC will need to move to get there.
|
||||
constexpr unsigned minimumWanderDistance = destinationTolerance * 2;
|
||||
|
||||
constexpr std::size_t maxIdleSize = 8;
|
||||
|
||||
inline int getCountBeforeReset(const MWWorld::ConstPtr& actor)
|
||||
{
|
||||
if (actor.getClass().isPureWaterCreature(actor) || actor.getClass().isPureFlyingCreature(actor))
|
||||
return 1;
|
||||
return COUNT_BEFORE_RESET;
|
||||
return countBeforeReset;
|
||||
}
|
||||
|
||||
osg::Vec3f getRandomPointAround(const osg::Vec3f& position, const float distance)
|
||||
|
@ -99,16 +100,42 @@ namespace MWMechanics
|
|||
|
||||
std::vector<unsigned char> getInitialIdle(const std::vector<unsigned char>& idle)
|
||||
{
|
||||
std::vector<unsigned char> result(MAX_IDLE_SIZE, 0);
|
||||
std::copy_n(idle.begin(), std::min(MAX_IDLE_SIZE, idle.size()), result.begin());
|
||||
std::vector<unsigned char> result(maxIdleSize, 0);
|
||||
std::copy_n(idle.begin(), std::min(maxIdleSize, idle.size()), result.begin());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> getInitialIdle(const unsigned char (&idle)[MAX_IDLE_SIZE])
|
||||
std::vector<unsigned char> getInitialIdle(const unsigned char (&idle)[maxIdleSize])
|
||||
{
|
||||
return std::vector<unsigned char>(std::begin(idle), std::end(idle));
|
||||
}
|
||||
|
||||
void trimAllowedPositions(const std::deque<osg::Vec3f>& path, std::vector<osg::Vec3f>& allowedPositions)
|
||||
{
|
||||
// TODO: how to add these back in once the door opens?
|
||||
// Idea: keep a list of detected closed doors (see aicombat.cpp)
|
||||
// Every now and then check whether one of the doors is opened. (maybe
|
||||
// at the end of playing idle?) If the door is opened then re-calculate
|
||||
// allowed positions starting from the spawn point.
|
||||
std::vector<osg::Vec3f> points(path.begin(), path.end());
|
||||
while (points.size() >= 2)
|
||||
{
|
||||
const osg::Vec3f point = points.back();
|
||||
for (std::size_t j = 0; j < allowedPositions.size(); j++)
|
||||
{
|
||||
// FIXME: doesn't handle a door with the same X/Y
|
||||
// coordinates but with a different Z
|
||||
if (std::abs(allowedPositions[j].x() - point.x()) <= 0.5
|
||||
&& std::abs(allowedPositions[j].y() - point.y()) <= 0.5)
|
||||
{
|
||||
allowedPositions.erase(allowedPositions.begin() + j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
points.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AiWanderStorage::AiWanderStorage()
|
||||
|
@ -118,9 +145,9 @@ namespace MWMechanics
|
|||
, mCanWanderAlongPathGrid(true)
|
||||
, mIdleAnimation(0)
|
||||
, mBadIdles()
|
||||
, mPopulateAvailableNodes(true)
|
||||
, mAllowedNodes()
|
||||
, mTrimCurrentNode(false)
|
||||
, mPopulateAvailablePositions(true)
|
||||
, mAllowedPositions()
|
||||
, mTrimCurrentPosition(false)
|
||||
, mCheckIdlePositionTimer(0)
|
||||
, mStuckCount(0)
|
||||
{
|
||||
|
@ -128,8 +155,8 @@ namespace MWMechanics
|
|||
|
||||
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat)
|
||||
: TypedAiPackage<AiWander>(repeat)
|
||||
, mDistance(std::max(0, distance))
|
||||
, mDuration(std::max(0, duration))
|
||||
, mDistance(static_cast<unsigned>(std::max(0, distance)))
|
||||
, mDuration(static_cast<unsigned>(std::max(0, duration)))
|
||||
, mRemainingDuration(duration)
|
||||
, mTimeOfDay(timeOfDay)
|
||||
, mIdle(getInitialIdle(idle))
|
||||
|
@ -215,20 +242,12 @@ namespace MWMechanics
|
|||
{
|
||||
const ESM::Pathgrid* pathgrid
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||
if (mUsePathgrid)
|
||||
{
|
||||
mPathFinder.buildPathByPathgrid(
|
||||
pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid));
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor);
|
||||
constexpr float endTolerance = 0;
|
||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||
mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid),
|
||||
agentBounds, navigatorFlags, areaCosts, endTolerance, PathType::Full);
|
||||
}
|
||||
const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor);
|
||||
constexpr float endTolerance = 0;
|
||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||
mPathFinder.buildPath(actor, pos.asVec3(), mDestination, getPathGridGraph(pathgrid), agentBounds,
|
||||
navigatorFlags, areaCosts, endTolerance, PathType::Full);
|
||||
|
||||
if (mPathFinder.isPathConstructed())
|
||||
storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid);
|
||||
|
@ -259,9 +278,6 @@ namespace MWMechanics
|
|||
|
||||
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
||||
{
|
||||
if (mDistance <= 0)
|
||||
storage.mCanWanderAlongPathGrid = false;
|
||||
|
||||
if (isPackageCompleted())
|
||||
{
|
||||
stopWalking(actor);
|
||||
|
@ -276,13 +292,15 @@ namespace MWMechanics
|
|||
mStoredInitialActorPosition = true;
|
||||
}
|
||||
|
||||
// Initialization to discover & store allowed node points for this actor.
|
||||
if (storage.mPopulateAvailableNodes)
|
||||
// Initialization to discover & store allowed positions points for this actor.
|
||||
if (storage.mPopulateAvailablePositions)
|
||||
{
|
||||
getAllowedNodes(actor, storage);
|
||||
fillAllowedPositions(actor, storage);
|
||||
}
|
||||
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||
|
||||
auto& prng = world.getPrng();
|
||||
if (canActorMoveByZAxis(actor) && mDistance > 0)
|
||||
{
|
||||
// Typically want to idle for a short time before the next wander
|
||||
|
@ -295,7 +313,7 @@ namespace MWMechanics
|
|||
}
|
||||
// If the package has a wander distance but no pathgrid is available,
|
||||
// randomly idle or wander near spawn point
|
||||
else if (storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually)
|
||||
else if (storage.mAllowedPositions.empty() && mDistance > 0 && !storage.mIsWanderingManually)
|
||||
{
|
||||
// Typically want to idle for a short time before the next wander
|
||||
if (Misc::Rng::rollDice(100, prng) >= 96)
|
||||
|
@ -307,7 +325,7 @@ namespace MWMechanics
|
|||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||
}
|
||||
}
|
||||
else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually)
|
||||
else if (storage.mAllowedPositions.empty() && !storage.mIsWanderingManually)
|
||||
{
|
||||
storage.mCanWanderAlongPathGrid = false;
|
||||
}
|
||||
|
@ -323,9 +341,9 @@ namespace MWMechanics
|
|||
// Construct a new path if there isn't one
|
||||
if (!mPathFinder.isPathConstructed())
|
||||
{
|
||||
if (!storage.mAllowedNodes.empty())
|
||||
if (!storage.mAllowedPositions.empty())
|
||||
{
|
||||
setPathToAnAllowedNode(actor, storage, pos);
|
||||
setPathToAnAllowedPosition(actor, storage, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -336,7 +354,7 @@ namespace MWMechanics
|
|||
|
||||
if (storage.mIsWanderingManually && storage.mState == AiWanderStorage::Wander_Walking
|
||||
&& (mPathFinder.getPathSize() == 0 || isDestinationHidden(actor, mPathFinder.getPath().back())
|
||||
|| isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back())))
|
||||
|| world.isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back())))
|
||||
completeManualWalking(actor, storage);
|
||||
|
||||
return false; // AiWander package not yet completed
|
||||
|
@ -366,12 +384,12 @@ namespace MWMechanics
|
|||
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
|
||||
const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
|
||||
const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor);
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto agentBounds = world->getPathfindingAgentBounds(actor);
|
||||
const auto navigator = world->getNavigator();
|
||||
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||
const auto agentBounds = world.getPathfindingAgentBounds(actor);
|
||||
const auto navigator = world.getNavigator();
|
||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
Misc::Rng::Generator& prng = world.getPrng();
|
||||
|
||||
do
|
||||
{
|
||||
|
@ -411,7 +429,7 @@ namespace MWMechanics
|
|||
if (isDestinationHidden(actor, mDestination))
|
||||
continue;
|
||||
|
||||
if (isAreaOccupiedByOtherActor(actor, mDestination))
|
||||
if (world.isAreaOccupiedByOtherActor(actor, mDestination))
|
||||
continue;
|
||||
|
||||
constexpr float endTolerance = 0;
|
||||
|
@ -491,17 +509,17 @@ namespace MWMechanics
|
|||
|
||||
void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||
{
|
||||
// Check if an idle actor is too far from all allowed nodes or too close to a door - if so start walking.
|
||||
// Check if an idle actor is too far from all allowed positions or too close to a door - if so start walking.
|
||||
storage.mCheckIdlePositionTimer += duration;
|
||||
|
||||
if (storage.mCheckIdlePositionTimer >= IDLE_POSITION_CHECK_INTERVAL && !isStationary())
|
||||
if (storage.mCheckIdlePositionTimer >= idlePositionCheckInterval && !isStationary())
|
||||
{
|
||||
storage.mCheckIdlePositionTimer = 0; // restart timer
|
||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f;
|
||||
if (proximityToDoor(actor, distance) || !isNearAllowedNode(actor, storage, distance))
|
||||
if (proximityToDoor(actor, distance) || !isNearAllowedPosition(actor, storage, distance))
|
||||
{
|
||||
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||
storage.mTrimCurrentNode = false; // just in case
|
||||
storage.mTrimCurrentPosition = false; // just in case
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -517,16 +535,14 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
bool AiWander::isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
|
||||
bool AiWander::isNearAllowedPosition(
|
||||
const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
|
||||
{
|
||||
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||
for (const ESM::Pathgrid::Point& node : storage.mAllowedNodes)
|
||||
{
|
||||
osg::Vec3f point(node.mX, node.mY, node.mZ);
|
||||
if ((actorPos - point).length2() < distance * distance)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const float squaredDistance = distance * distance;
|
||||
return std::ranges::find_if(storage.mAllowedPositions, [&](const osg::Vec3& v) {
|
||||
return (actorPos - v).length2() < squaredDistance;
|
||||
}) != storage.mAllowedPositions.end();
|
||||
}
|
||||
|
||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration,
|
||||
|
@ -535,7 +551,7 @@ namespace MWMechanics
|
|||
// Is there no destination or are we there yet?
|
||||
if ((!mPathFinder.isPathConstructed())
|
||||
|| pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, supportedMovementDirections,
|
||||
DESTINATION_TOLERANCE))
|
||||
destinationTolerance))
|
||||
{
|
||||
stopWalking(actor);
|
||||
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||
|
@ -586,8 +602,8 @@ namespace MWMechanics
|
|||
if (proximityToDoor(actor, distance))
|
||||
{
|
||||
// remove allowed points then select another random destination
|
||||
storage.mTrimCurrentNode = true;
|
||||
trimAllowedNodes(storage.mAllowedNodes, mPathFinder);
|
||||
storage.mTrimCurrentPosition = true;
|
||||
trimAllowedPositions(mPathFinder.getPath(), storage.mAllowedPositions);
|
||||
mObstacleCheck.clear();
|
||||
stopWalking(actor);
|
||||
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||
|
@ -606,67 +622,67 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void AiWander::setPathToAnAllowedNode(
|
||||
void AiWander::setPathToAnAllowedPosition(
|
||||
const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
|
||||
{
|
||||
auto world = MWBase::Environment::get().getWorld();
|
||||
auto& prng = world->getPrng();
|
||||
unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
|
||||
const ESM::Pathgrid::Point& dest = storage.mAllowedNodes[randNode];
|
||||
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||
Misc::Rng::Generator& prng = world.getPrng();
|
||||
const std::size_t randomAllowedPositionIndex
|
||||
= static_cast<std::size_t>(Misc::Rng::rollDice(storage.mAllowedPositions.size(), prng));
|
||||
const osg::Vec3f randomAllowedPosition = storage.mAllowedPositions[randomAllowedPositionIndex];
|
||||
|
||||
const osg::Vec3f start = actorPos.asVec3();
|
||||
|
||||
// don't take shortcuts for wandering
|
||||
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||
const osg::Vec3f destVec3f = PathFinder::makeOsgVec3(dest);
|
||||
mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(pathgrid));
|
||||
const MWWorld::Cell& cell = *actor.getCell()->getCell();
|
||||
const ESM::Pathgrid* pathgrid = world.getStore().get<ESM::Pathgrid>().search(cell);
|
||||
const PathgridGraph& pathgridGraph = getPathGridGraph(pathgrid);
|
||||
|
||||
if (mPathFinder.isPathConstructed())
|
||||
const Misc::CoordinateConverter converter = Misc::makeCoordinateConverter(cell);
|
||||
std::deque<ESM::Pathgrid::Point> path
|
||||
= pathgridGraph.aStarSearch(Misc::getClosestPoint(*pathgrid, converter.toLocalVec3(start)),
|
||||
Misc::getClosestPoint(*pathgrid, converter.toLocalVec3(randomAllowedPosition)));
|
||||
|
||||
// Choose a different position and delete this one from possible positions because it is uncreachable:
|
||||
if (path.empty())
|
||||
{
|
||||
mDestination = destVec3f;
|
||||
mHasDestination = true;
|
||||
mUsePathgrid = true;
|
||||
// Remove this node as an option and add back the previously used node (stops NPC from picking the same
|
||||
// node):
|
||||
ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode];
|
||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
|
||||
// check if mCurrentNode was taken out of mAllowedNodes
|
||||
if (storage.mTrimCurrentNode && storage.mAllowedNodes.size() > 1)
|
||||
storage.mTrimCurrentNode = false;
|
||||
else
|
||||
storage.mAllowedNodes.push_back(storage.mCurrentNode);
|
||||
storage.mCurrentNode = temp;
|
||||
|
||||
storage.setState(AiWanderStorage::Wander_Walking);
|
||||
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
|
||||
return;
|
||||
}
|
||||
// Choose a different node and delete this one from possible nodes because it is uncreachable:
|
||||
|
||||
// Drop nearest pathgrid point.
|
||||
path.pop_front();
|
||||
|
||||
std::vector<osg::Vec3f> checkpoints(path.size());
|
||||
for (std::size_t i = 0; i < path.size(); ++i)
|
||||
checkpoints[i] = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(path[i]));
|
||||
|
||||
const DetourNavigator::AgentBounds agentBounds = world.getPathfindingAgentBounds(actor);
|
||||
const DetourNavigator::Flags flags = getNavigatorFlags(actor);
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, flags);
|
||||
constexpr float endTolerance = 0;
|
||||
mPathFinder.buildPath(actor, start, randomAllowedPosition, pathgridGraph, agentBounds, flags, areaCosts,
|
||||
endTolerance, PathType::Full, checkpoints);
|
||||
|
||||
if (!mPathFinder.isPathConstructed())
|
||||
{
|
||||
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
mDestination = randomAllowedPosition;
|
||||
mHasDestination = true;
|
||||
mUsePathgrid = true;
|
||||
// Remove this position as an option and add back the previously used position (stops NPC from picking the
|
||||
// same position):
|
||||
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
|
||||
// check if mCurrentPosition was taken out of mAllowedPositions
|
||||
if (storage.mTrimCurrentPosition && storage.mAllowedPositions.size() > 1)
|
||||
storage.mTrimCurrentPosition = false;
|
||||
else
|
||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
|
||||
}
|
||||
storage.mAllowedPositions.push_back(storage.mCurrentPosition);
|
||||
storage.mCurrentPosition = randomAllowedPosition;
|
||||
|
||||
void AiWander::trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder)
|
||||
{
|
||||
// TODO: how to add these back in once the door opens?
|
||||
// Idea: keep a list of detected closed doors (see aicombat.cpp)
|
||||
// Every now and then check whether one of the doors is opened. (maybe
|
||||
// at the end of playing idle?) If the door is opened then re-calculate
|
||||
// allowed nodes starting from the spawn point.
|
||||
auto paths = pathfinder.getPath();
|
||||
while (paths.size() >= 2)
|
||||
{
|
||||
const auto pt = paths.back();
|
||||
for (unsigned int j = 0; j < nodes.size(); j++)
|
||||
{
|
||||
// FIXME: doesn't handle a door with the same X/Y
|
||||
// coordinates but with a different Z
|
||||
if (std::abs(nodes[j].mX - pt.x()) <= 0.5 && std::abs(nodes[j].mY - pt.y()) <= 0.5)
|
||||
{
|
||||
nodes.erase(nodes.begin() + j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
paths.pop_back();
|
||||
}
|
||||
storage.setState(AiWanderStorage::Wander_Walking);
|
||||
}
|
||||
|
||||
void AiWander::stopWalking(const MWWorld::Ptr& actor)
|
||||
|
@ -742,20 +758,20 @@ namespace MWMechanics
|
|||
return;
|
||||
|
||||
AiWanderStorage& storage = state.get<AiWanderStorage>();
|
||||
if (storage.mPopulateAvailableNodes)
|
||||
getAllowedNodes(actor, storage);
|
||||
if (storage.mPopulateAvailablePositions)
|
||||
fillAllowedPositions(actor, storage);
|
||||
|
||||
if (storage.mAllowedNodes.empty())
|
||||
if (storage.mAllowedPositions.empty())
|
||||
return;
|
||||
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
int index = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
|
||||
ESM::Pathgrid::Point worldDest = storage.mAllowedNodes[index];
|
||||
int index = Misc::Rng::rollDice(storage.mAllowedPositions.size(), prng);
|
||||
const osg::Vec3f worldDest = storage.mAllowedPositions[index];
|
||||
const Misc::CoordinateConverter converter = Misc::makeCoordinateConverter(*actor.getCell()->getCell());
|
||||
ESM::Pathgrid::Point dest = converter.toLocalPoint(worldDest);
|
||||
osg::Vec3f dest = converter.toLocalVec3(worldDest);
|
||||
|
||||
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
|
||||
PathFinder::makeOsgVec3(worldDest), 60);
|
||||
const bool isPathGridOccupied
|
||||
= MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(worldDest, 60);
|
||||
|
||||
// add offset only if the selected pathgrid is occupied by another actor
|
||||
if (isPathGridOccupied)
|
||||
|
@ -775,19 +791,17 @@ namespace MWMechanics
|
|||
const ESM::Pathgrid::Point& connDest = points[randomIndex];
|
||||
|
||||
// add an offset towards random neighboring node
|
||||
osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest);
|
||||
float length = dir.length();
|
||||
osg::Vec3f dir = Misc::Convert::makeOsgVec3f(connDest) - dest;
|
||||
const float length = dir.length();
|
||||
dir.normalize();
|
||||
|
||||
for (int j = 1; j <= 3; j++)
|
||||
{
|
||||
// move for 5-15% towards random neighboring node
|
||||
dest
|
||||
= PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
|
||||
worldDest = converter.toWorldPoint(dest);
|
||||
dest = dest + dir * (j * 5 * length / 100.f);
|
||||
|
||||
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
|
||||
PathFinder::makeOsgVec3(worldDest), 60);
|
||||
converter.toWorldVec3(dest), 60);
|
||||
|
||||
if (!isOccupied)
|
||||
break;
|
||||
|
@ -807,19 +821,18 @@ namespace MWMechanics
|
|||
|
||||
// place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be
|
||||
// underground. Adding 20 in adjustPosition() is not enough.
|
||||
dest.mZ += 60;
|
||||
dest.z() += 60;
|
||||
|
||||
converter.toWorld(dest);
|
||||
|
||||
state.reset();
|
||||
|
||||
osg::Vec3f pos(static_cast<float>(dest.mX), static_cast<float>(dest.mY), static_cast<float>(dest.mZ));
|
||||
MWBase::Environment::get().getWorld()->moveObject(actor, pos);
|
||||
MWBase::Environment::get().getWorld()->moveObject(actor, dest);
|
||||
actor.getClass().adjustPosition(actor, false);
|
||||
}
|
||||
|
||||
void AiWander::getNeighbouringNodes(
|
||||
ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points)
|
||||
const osg::Vec3f& dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points)
|
||||
{
|
||||
const ESM::Pathgrid* pathgrid
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*currentCell->getCell());
|
||||
|
@ -827,19 +840,19 @@ namespace MWMechanics
|
|||
if (pathgrid == nullptr || pathgrid->mPoints.empty())
|
||||
return;
|
||||
|
||||
size_t index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
|
||||
const size_t index = Misc::getClosestPoint(*pathgrid, dest);
|
||||
|
||||
getPathGridGraph(pathgrid).getNeighbouringPoints(index, points);
|
||||
}
|
||||
|
||||
void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||
void AiWander::fillAllowedPositions(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||
{
|
||||
// infrequently used, therefore no benefit in caching it as a member
|
||||
const MWWorld::CellStore* cellStore = actor.getCell();
|
||||
const ESM::Pathgrid* pathgrid
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*cellStore->getCell());
|
||||
|
||||
storage.mAllowedNodes.clear();
|
||||
storage.mAllowedPositions.clear();
|
||||
|
||||
// If there is no path this actor doesn't go anywhere. See:
|
||||
// https://forum.openmw.org/viewtopic.php?t=1556
|
||||
|
@ -860,34 +873,35 @@ namespace MWMechanics
|
|||
const osg::Vec3f npcPos = converter.toLocalVec3(mInitialActorPosition);
|
||||
|
||||
// Find closest pathgrid point
|
||||
size_t closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos);
|
||||
const std::size_t closestPointIndex = Misc::getClosestPoint(*pathgrid, npcPos);
|
||||
|
||||
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
|
||||
// mAllowedPositions for this actor with pathgrid point indexes based on mDistance
|
||||
// and if the point is connected to the closest current point
|
||||
// NOTE: mPoints is in local coordinates
|
||||
size_t pointIndex = 0;
|
||||
for (size_t counter = 0; counter < pathgrid->mPoints.size(); counter++)
|
||||
{
|
||||
osg::Vec3f nodePos(PathFinder::makeOsgVec3(pathgrid->mPoints[counter]));
|
||||
const osg::Vec3f nodePos = Misc::Convert::makeOsgVec3f(pathgrid->mPoints[counter]);
|
||||
if ((npcPos - nodePos).length2() <= mDistance * mDistance
|
||||
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, counter))
|
||||
{
|
||||
storage.mAllowedNodes.push_back(converter.toWorldPoint(pathgrid->mPoints[counter]));
|
||||
storage.mAllowedPositions.push_back(
|
||||
Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid->mPoints[counter])));
|
||||
pointIndex = counter;
|
||||
}
|
||||
}
|
||||
if (storage.mAllowedNodes.size() == 1)
|
||||
if (storage.mAllowedPositions.size() == 1)
|
||||
{
|
||||
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(mInitialActorPosition));
|
||||
storage.mAllowedPositions.push_back(mInitialActorPosition);
|
||||
addNonPathGridAllowedPoints(pathgrid, pointIndex, storage, converter);
|
||||
}
|
||||
if (!storage.mAllowedNodes.empty())
|
||||
if (!storage.mAllowedPositions.empty())
|
||||
{
|
||||
setCurrentNodeToClosestAllowedNode(storage);
|
||||
setCurrentPositionToClosestAllowedPosition(storage);
|
||||
}
|
||||
}
|
||||
|
||||
storage.mPopulateAvailableNodes = false;
|
||||
storage.mPopulateAvailablePositions = false;
|
||||
}
|
||||
|
||||
// When only one path grid point in wander distance,
|
||||
|
@ -901,44 +915,44 @@ namespace MWMechanics
|
|||
{
|
||||
if (edge.mV0 == pointIndex)
|
||||
{
|
||||
AddPointBetweenPathGridPoints(converter.toWorldPoint(pathGrid->mPoints[edge.mV0]),
|
||||
addPositionBetweenPathgridPoints(converter.toWorldPoint(pathGrid->mPoints[edge.mV0]),
|
||||
converter.toWorldPoint(pathGrid->mPoints[edge.mV1]), storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AiWander::AddPointBetweenPathGridPoints(
|
||||
void AiWander::addPositionBetweenPathgridPoints(
|
||||
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage)
|
||||
{
|
||||
osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start);
|
||||
osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart;
|
||||
osg::Vec3f vectorStart = Misc::Convert::makeOsgVec3f(start);
|
||||
osg::Vec3f delta = Misc::Convert::makeOsgVec3f(end) - vectorStart;
|
||||
float length = delta.length();
|
||||
delta.normalize();
|
||||
|
||||
int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE);
|
||||
unsigned distance = std::max(mDistance / 2, minimumWanderDistance);
|
||||
|
||||
// must not travel longer than distance between waypoints or NPC goes past waypoint
|
||||
distance = std::min(distance, static_cast<int>(length));
|
||||
distance = std::min(distance, static_cast<unsigned>(length));
|
||||
delta *= distance;
|
||||
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(vectorStart + delta));
|
||||
storage.mAllowedPositions.push_back(vectorStart + delta);
|
||||
}
|
||||
|
||||
void AiWander::setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage)
|
||||
void AiWander::setCurrentPositionToClosestAllowedPosition(AiWanderStorage& storage)
|
||||
{
|
||||
float distanceToClosestNode = std::numeric_limits<float>::max();
|
||||
float distanceToClosestPosition = std::numeric_limits<float>::max();
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < storage.mAllowedNodes.size(); ++i)
|
||||
for (size_t i = 0; i < storage.mAllowedPositions.size(); ++i)
|
||||
{
|
||||
osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[i]));
|
||||
float tempDist = (mInitialActorPosition - nodePos).length2();
|
||||
if (tempDist < distanceToClosestNode)
|
||||
const osg::Vec3f position = storage.mAllowedPositions[i];
|
||||
const float tempDist = (mInitialActorPosition - position).length2();
|
||||
if (tempDist < distanceToClosestPosition)
|
||||
{
|
||||
index = i;
|
||||
distanceToClosestNode = tempDist;
|
||||
distanceToClosestPosition = tempDist;
|
||||
}
|
||||
}
|
||||
storage.mCurrentNode = storage.mAllowedNodes[index];
|
||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + index);
|
||||
storage.mCurrentPosition = storage.mAllowedPositions[index];
|
||||
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + index);
|
||||
}
|
||||
|
||||
void AiWander::writeState(ESM::AiSequence::AiSequence& sequence) const
|
||||
|
@ -970,8 +984,8 @@ namespace MWMechanics
|
|||
|
||||
AiWander::AiWander(const ESM::AiSequence::AiWander* wander)
|
||||
: TypedAiPackage<AiWander>(makeDefaultOptions().withRepeat(wander->mData.mShouldRepeat != 0))
|
||||
, mDistance(std::max(static_cast<short>(0), wander->mData.mDistance))
|
||||
, mDuration(std::max(static_cast<short>(0), wander->mData.mDuration))
|
||||
, mDistance(static_cast<unsigned>(std::max(static_cast<short>(0), wander->mData.mDistance)))
|
||||
, mDuration(static_cast<unsigned>(std::max(static_cast<short>(0), wander->mData.mDuration)))
|
||||
, mRemainingDuration(wander->mDurationData.mRemainingDuration)
|
||||
, mTimeOfDay(wander->mData.mTimeOfDay)
|
||||
, mIdle(getInitialIdle(wander->mData.mIdle))
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
#ifndef GAME_MWMECHANICS_AIWANDER_H
|
||||
#define GAME_MWMECHANICS_AIWANDER_H
|
||||
|
||||
#include "typedaipackage.hpp"
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "aitemporarybase.hpp"
|
||||
#include "aitimer.hpp"
|
||||
#include "pathfinding.hpp"
|
||||
#include "typedaipackage.hpp"
|
||||
|
||||
#include <components/esm3/loadpgrd.hpp>
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -51,14 +52,13 @@ namespace MWMechanics
|
|||
unsigned short mIdleAnimation;
|
||||
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
|
||||
|
||||
// do we need to calculate allowed nodes based on mDistance
|
||||
bool mPopulateAvailableNodes;
|
||||
bool mPopulateAvailablePositions;
|
||||
|
||||
// allowed pathgrid nodes based on mDistance from the spawn point
|
||||
std::vector<ESM::Pathgrid::Point> mAllowedNodes;
|
||||
// allowed destination positions based on mDistance from the spawn point
|
||||
std::vector<osg::Vec3f> mAllowedPositions;
|
||||
|
||||
ESM::Pathgrid::Point mCurrentNode;
|
||||
bool mTrimCurrentNode;
|
||||
osg::Vec3f mCurrentPosition;
|
||||
bool mTrimCurrentPosition;
|
||||
|
||||
float mCheckIdlePositionTimer;
|
||||
int mStuckCount;
|
||||
|
@ -132,7 +132,8 @@ namespace MWMechanics
|
|||
bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||
int getRandomIdle() const;
|
||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||
void setPathToAnAllowedPosition(
|
||||
const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration,
|
||||
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage);
|
||||
|
@ -145,28 +146,27 @@ namespace MWMechanics
|
|||
void wanderNearStart(const MWWorld::Ptr& actor, AiWanderStorage& storage, int wanderDistance);
|
||||
bool destinationIsAtWater(const MWWorld::Ptr& actor, const osg::Vec3f& destination);
|
||||
void completeManualWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
bool isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const;
|
||||
bool isNearAllowedPosition(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const;
|
||||
|
||||
const int mDistance; // how far the actor can wander from the spawn point
|
||||
const int mDuration;
|
||||
// how far the actor can wander from the spawn point
|
||||
const unsigned mDistance;
|
||||
const unsigned mDuration;
|
||||
float mRemainingDuration;
|
||||
const int mTimeOfDay;
|
||||
const std::vector<unsigned char> mIdle;
|
||||
|
||||
bool mStoredInitialActorPosition;
|
||||
osg::Vec3f
|
||||
mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell
|
||||
// Note: an original engine does not reset coordinates even when actor changes a cell
|
||||
osg::Vec3f mInitialActorPosition;
|
||||
|
||||
bool mHasDestination;
|
||||
osg::Vec3f mDestination;
|
||||
bool mUsePathgrid;
|
||||
|
||||
void getNeighbouringNodes(
|
||||
ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
|
||||
const osg::Vec3f& dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
|
||||
|
||||
void getAllowedNodes(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
|
||||
void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder);
|
||||
void fillAllowedPositions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
|
||||
// constants for converting idleSelect values into groupNames
|
||||
enum GroupIndex
|
||||
|
@ -175,12 +175,12 @@ namespace MWMechanics
|
|||
GroupIndex_MaxIdle = 9
|
||||
};
|
||||
|
||||
void setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage);
|
||||
void setCurrentPositionToClosestAllowedPosition(AiWanderStorage& storage);
|
||||
|
||||
void addNonPathGridAllowedPoints(const ESM::Pathgrid* pathGrid, size_t pointIndex, AiWanderStorage& storage,
|
||||
const Misc::CoordinateConverter& converter);
|
||||
|
||||
void AddPointBetweenPathGridPoints(
|
||||
void addPositionBetweenPathgridPoints(
|
||||
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage);
|
||||
|
||||
/// lookup table for converting idleSelect value to groupName
|
||||
|
|
|
@ -2310,17 +2310,13 @@ namespace MWMechanics
|
|||
}
|
||||
else
|
||||
{
|
||||
// Do not play turning animation for player if rotation speed is very slow.
|
||||
// Actual threshold should take framerate in account.
|
||||
float rotationThreshold = (isPlayer ? 0.015f : 0.001f) * 60 * duration;
|
||||
|
||||
// It seems only bipedal actors use turning animations.
|
||||
// Also do not use turning animations in the first-person view and when sneaking.
|
||||
if (!sneak && !isFirstPersonPlayer && isBiped)
|
||||
{
|
||||
if (effectiveRotation > rotationThreshold)
|
||||
if (effectiveRotation > 0.f)
|
||||
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
|
||||
else if (effectiveRotation < -rotationThreshold)
|
||||
else if (effectiveRotation < 0.f)
|
||||
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
|
||||
}
|
||||
}
|
||||
|
@ -2346,34 +2342,19 @@ namespace MWMechanics
|
|||
vec.y() *= std::sqrt(1.0f - swimUpwardCoef * swimUpwardCoef);
|
||||
}
|
||||
|
||||
// Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering
|
||||
if (isPlayer)
|
||||
if (isBiped)
|
||||
{
|
||||
float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f;
|
||||
float complete;
|
||||
bool animPlaying = mAnimation->getInfo(mCurrentMovement, &complete);
|
||||
if (movestate == CharState_None && jumpstate == JumpState_None && isTurning())
|
||||
{
|
||||
if (animPlaying && complete < threshold)
|
||||
movestate = mMovementState;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isBiped)
|
||||
{
|
||||
if (mTurnAnimationThreshold > 0)
|
||||
mTurnAnimationThreshold -= duration;
|
||||
if (mTurnAnimationThreshold > 0)
|
||||
mTurnAnimationThreshold -= duration;
|
||||
|
||||
if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft
|
||||
|| movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft)
|
||||
{
|
||||
mTurnAnimationThreshold = 0.05f;
|
||||
}
|
||||
else if (movestate == CharState_None && isTurning() && mTurnAnimationThreshold > 0)
|
||||
{
|
||||
movestate = mMovementState;
|
||||
}
|
||||
if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft
|
||||
|| movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft)
|
||||
{
|
||||
mTurnAnimationThreshold = 0.05f;
|
||||
}
|
||||
else if (movestate == CharState_None && isTurning() && mTurnAnimationThreshold > 0)
|
||||
{
|
||||
movestate = mMovementState;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2402,11 +2383,10 @@ namespace MWMechanics
|
|||
|
||||
if (isTurning())
|
||||
{
|
||||
// Adjust animation speed from 1.0 to 1.5 multiplier
|
||||
if (duration > 0)
|
||||
{
|
||||
float turnSpeed = std::min(1.5f, std::abs(rot.z()) / duration / static_cast<float>(osg::PI));
|
||||
mAnimation->adjustSpeedMult(mCurrentMovement, std::max(turnSpeed, 1.0f));
|
||||
mAnimation->adjustSpeedMult(mCurrentMovement, turnSpeed);
|
||||
}
|
||||
}
|
||||
else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed)
|
||||
|
|
|
@ -106,21 +106,6 @@ namespace MWMechanics
|
|||
return visitor.mResult;
|
||||
}
|
||||
|
||||
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, bool ignorePlayer,
|
||||
std::vector<MWWorld::Ptr>* occupyingActors)
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const osg::Vec3f halfExtents = world->getPathfindingAgentBounds(actor).mHalfExtents;
|
||||
const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
|
||||
if (ignorePlayer)
|
||||
{
|
||||
const std::array ignore{ actor, world->getPlayerConstPtr() };
|
||||
return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors);
|
||||
}
|
||||
const std::array ignore{ actor };
|
||||
return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors);
|
||||
}
|
||||
|
||||
ObstacleCheck::ObstacleCheck()
|
||||
: mEvadeDirectionIndex(std::size(evadeDirections) - 1)
|
||||
{
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
|
@ -24,9 +22,6 @@ namespace MWMechanics
|
|||
/** \return Pointer to the door, or empty pointer if none exists **/
|
||||
const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist);
|
||||
|
||||
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination,
|
||||
bool ignorePlayer = false, std::vector<MWWorld::Ptr>* occupyingActors = nullptr);
|
||||
|
||||
class ObstacleCheck
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <components/detournavigator/navigatorutils.hpp>
|
||||
#include <components/misc/coordinateconverter.hpp>
|
||||
#include <components/misc/math.hpp>
|
||||
#include <components/misc/pathgridutils.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
@ -38,7 +39,7 @@ namespace
|
|||
// points to a quadtree may help
|
||||
for (size_t counter = 0; counter < grid->mPoints.size(); counter++)
|
||||
{
|
||||
float potentialDistBetween = MWMechanics::PathFinder::distanceSquared(grid->mPoints[counter], pos);
|
||||
float potentialDistBetween = Misc::distanceSquared(grid->mPoints[counter], pos);
|
||||
if (potentialDistBetween < closestDistanceReachable)
|
||||
{
|
||||
// found a closer one
|
||||
|
@ -197,7 +198,7 @@ namespace MWMechanics
|
|||
// point right behind the wall that is closer than any pathgrid
|
||||
// point outside the wall
|
||||
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
||||
size_t startNode = getClosestPoint(pathgrid, startPointInLocalCoords);
|
||||
const size_t startNode = Misc::getClosestPoint(*pathgrid, startPointInLocalCoords);
|
||||
|
||||
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
||||
std::pair<size_t, bool> endNode
|
||||
|
@ -206,8 +207,8 @@ namespace MWMechanics
|
|||
// if it's shorter for actor to travel from start to end, than to travel from either
|
||||
// start or end to nearest pathgrid point, just travel from start to end.
|
||||
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
||||
float endTolastNodeLength2 = distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||
float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||
float endTolastNodeLength2 = Misc::distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||
float startTo1stNodeLength2 = Misc::distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
||||
{
|
||||
*out++ = endPoint;
|
||||
|
@ -223,7 +224,7 @@ namespace MWMechanics
|
|||
{
|
||||
ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]);
|
||||
converter.toWorld(temp);
|
||||
*out++ = makeOsgVec3(temp);
|
||||
*out++ = Misc::Convert::makeOsgVec3f(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -234,8 +235,8 @@ namespace MWMechanics
|
|||
if (path.size() > 1)
|
||||
{
|
||||
ESM::Pathgrid::Point secondNode = *(++path.begin());
|
||||
osg::Vec3f firstNodeVec3f = makeOsgVec3(pathgrid->mPoints[startNode]);
|
||||
osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode);
|
||||
osg::Vec3f firstNodeVec3f = Misc::Convert::makeOsgVec3f(pathgrid->mPoints[startNode]);
|
||||
osg::Vec3f secondNodeVec3f = Misc::Convert::makeOsgVec3f(secondNode);
|
||||
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
||||
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
||||
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
||||
|
@ -259,7 +260,7 @@ namespace MWMechanics
|
|||
// convert supplied path to world coordinates
|
||||
std::transform(path.begin(), path.end(), out, [&](ESM::Pathgrid::Point& point) {
|
||||
converter.toWorld(point);
|
||||
return makeOsgVec3(point);
|
||||
return Misc::Convert::makeOsgVec3f(point);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -359,26 +360,16 @@ namespace MWMechanics
|
|||
mConstructed = true;
|
||||
}
|
||||
|
||||
void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
||||
{
|
||||
mPath.clear();
|
||||
mCell = cell;
|
||||
|
||||
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
||||
|
||||
mConstructed = !mPath.empty();
|
||||
}
|
||||
|
||||
void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType,
|
||||
std::span<const osg::Vec3f> checkpoints)
|
||||
{
|
||||
mPath.clear();
|
||||
|
||||
// If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path
|
||||
DetourNavigator::Status status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags,
|
||||
areaCosts, endTolerance, pathType, std::back_inserter(mPath));
|
||||
areaCosts, endTolerance, pathType, checkpoints, std::back_inserter(mPath));
|
||||
|
||||
if (status != DetourNavigator::Status::Success)
|
||||
mPath.clear();
|
||||
|
@ -390,19 +381,19 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
||||
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType, std::span<const osg::Vec3f> checkpoints)
|
||||
{
|
||||
mPath.clear();
|
||||
mCell = cell;
|
||||
mCell = actor.getCell();
|
||||
|
||||
DetourNavigator::Status status = DetourNavigator::Status::NavMeshNotFound;
|
||||
|
||||
if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor))
|
||||
{
|
||||
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags, areaCosts, endTolerance,
|
||||
pathType, std::back_inserter(mPath));
|
||||
pathType, checkpoints, std::back_inserter(mPath));
|
||||
if (status != DetourNavigator::Status::Success)
|
||||
mPath.clear();
|
||||
}
|
||||
|
@ -411,7 +402,7 @@ namespace MWMechanics
|
|||
&& (flags & DetourNavigator::Flag_usePathgrid) == 0)
|
||||
{
|
||||
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds,
|
||||
flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType,
|
||||
flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, checkpoints,
|
||||
std::back_inserter(mPath));
|
||||
if (status != DetourNavigator::Status::Success)
|
||||
mPath.clear();
|
||||
|
@ -429,12 +420,13 @@ namespace MWMechanics
|
|||
DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
||||
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType, std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
||||
PathType pathType, std::span<const osg::Vec3f> checkpoints,
|
||||
std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto navigator = world->getNavigator();
|
||||
const auto status = DetourNavigator::findPath(
|
||||
*navigator, agentBounds, startPoint, endPoint, flags, areaCosts, endTolerance, out);
|
||||
const MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||
const DetourNavigator::Navigator& navigator = *world.getNavigator();
|
||||
const DetourNavigator::Status status = DetourNavigator::findPath(
|
||||
navigator, agentBounds, startPoint, endPoint, flags, areaCosts, endTolerance, checkpoints, out);
|
||||
|
||||
if (pathType == PathType::Partial && status == DetourNavigator::Status::PartialPath)
|
||||
return DetourNavigator::Status::Success;
|
||||
|
@ -451,9 +443,9 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||
const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
||||
const osg::Vec3f& endPoint, const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType)
|
||||
{
|
||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||
const auto maxDistance
|
||||
|
@ -461,9 +453,9 @@ namespace MWMechanics
|
|||
const auto startToEnd = endPoint - startPoint;
|
||||
const auto distance = startToEnd.length();
|
||||
if (distance <= maxDistance)
|
||||
return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, agentBounds, flags, areaCosts,
|
||||
endTolerance, pathType);
|
||||
return buildPath(
|
||||
actor, startPoint, endPoint, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType);
|
||||
const auto end = startPoint + startToEnd * maxDistance / distance;
|
||||
buildPath(actor, startPoint, end, cell, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType);
|
||||
buildPath(actor, startPoint, end, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
#include <cassert>
|
||||
#include <deque>
|
||||
#include <iterator>
|
||||
#include <span>
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <components/detournavigator/areatype.hpp>
|
||||
#include <components/detournavigator/flags.hpp>
|
||||
#include <components/detournavigator/status.hpp>
|
||||
#include <components/esm/position.hpp>
|
||||
#include <components/esm3/loadpgrd.hpp>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
|
@ -102,23 +103,20 @@ namespace MWMechanics
|
|||
|
||||
void buildStraightPath(const osg::Vec3f& endPoint);
|
||||
|
||||
void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
|
||||
|
||||
void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType);
|
||||
PathType pathType, std::span<const osg::Vec3f> checkpoints = {});
|
||||
|
||||
void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType);
|
||||
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType, std::span<const osg::Vec3f> checkpoints = {});
|
||||
|
||||
void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType);
|
||||
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType);
|
||||
|
||||
/// Remove front point if exist and within tolerance
|
||||
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
||||
|
@ -145,61 +143,6 @@ namespace MWMechanics
|
|||
mPath.push_back(point);
|
||||
}
|
||||
|
||||
/// utility function to convert a osg::Vec3f to a Pathgrid::Point
|
||||
static ESM::Pathgrid::Point makePathgridPoint(const osg::Vec3f& v)
|
||||
{
|
||||
return ESM::Pathgrid::Point(static_cast<int>(v[0]), static_cast<int>(v[1]), static_cast<int>(v[2]));
|
||||
}
|
||||
|
||||
/// utility function to convert an ESM::Position to a Pathgrid::Point
|
||||
static ESM::Pathgrid::Point makePathgridPoint(const ESM::Position& p)
|
||||
{
|
||||
return ESM::Pathgrid::Point(
|
||||
static_cast<int>(p.pos[0]), static_cast<int>(p.pos[1]), static_cast<int>(p.pos[2]));
|
||||
}
|
||||
|
||||
static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p)
|
||||
{
|
||||
return osg::Vec3f(static_cast<float>(p.mX), static_cast<float>(p.mY), static_cast<float>(p.mZ));
|
||||
}
|
||||
|
||||
// Slightly cheaper version for comparisons.
|
||||
// Caller needs to be careful for very short distances (i.e. less than 1)
|
||||
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
|
||||
//
|
||||
static float distanceSquared(const ESM::Pathgrid::Point& point, const osg::Vec3f& pos)
|
||||
{
|
||||
return (MWMechanics::PathFinder::makeOsgVec3(point) - pos).length2();
|
||||
}
|
||||
|
||||
// Return the closest pathgrid point index from the specified position
|
||||
// coordinates. NOTE: Does not check if there is a sensible way to get there
|
||||
// (e.g. a cliff in front).
|
||||
//
|
||||
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
|
||||
//
|
||||
static size_t getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
|
||||
{
|
||||
assert(grid && !grid->mPoints.empty());
|
||||
|
||||
float distanceBetween = distanceSquared(grid->mPoints[0], pos);
|
||||
size_t closestIndex = 0;
|
||||
|
||||
// TODO: if this full scan causes performance problems mapping pathgrid
|
||||
// points to a quadtree may help
|
||||
for (size_t counter = 1; counter < grid->mPoints.size(); counter++)
|
||||
{
|
||||
float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
|
||||
if (potentialDistBetween < distanceBetween)
|
||||
{
|
||||
distanceBetween = potentialDistBetween;
|
||||
closestIndex = counter;
|
||||
}
|
||||
}
|
||||
|
||||
return closestIndex;
|
||||
}
|
||||
|
||||
private:
|
||||
bool mConstructed = false;
|
||||
std::deque<osg::Vec3f> mPath;
|
||||
|
@ -211,7 +154,8 @@ namespace MWMechanics
|
|||
[[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
||||
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType, std::back_insert_iterator<std::deque<osg::Vec3f>> out);
|
||||
PathType pathType, std::span<const osg::Vec3f> checkpoints,
|
||||
std::back_insert_iterator<std::deque<osg::Vec3f>> out);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -215,10 +215,6 @@ namespace MWMechanics
|
|||
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||
effect.mDuration = hasDuration ? static_cast<float>(enam.mData.mDuration) : 1.f;
|
||||
|
||||
bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce;
|
||||
if (!appliedOnce)
|
||||
effect.mDuration = std::max(1.f, effect.mDuration);
|
||||
|
||||
effect.mTimeLeft = effect.mDuration;
|
||||
|
||||
// add to list of active effects, to apply in next frame
|
||||
|
|
|
@ -377,8 +377,11 @@ namespace
|
|||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||
return MWMechanics::MagicApplicationResult::Type::REMOVED;
|
||||
}
|
||||
effect.mMinMagnitude *= magnitudeMult;
|
||||
effect.mMaxMagnitude *= magnitudeMult;
|
||||
else if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||
{
|
||||
effect.mMinMagnitude *= magnitudeMult;
|
||||
effect.mMaxMagnitude *= magnitudeMult;
|
||||
}
|
||||
}
|
||||
return MWMechanics::MagicApplicationResult::Type::APPLIED;
|
||||
}
|
||||
|
@ -1011,11 +1014,13 @@ namespace MWMechanics
|
|||
else
|
||||
{
|
||||
// Morrowind.exe doesn't apply magic effects while the menu is open, we do because we like to see stats
|
||||
// updated instantly. We don't want to teleport instantly though
|
||||
// updated instantly. We don't want to teleport instantly though. Nor do we want to force players to drink
|
||||
// invisibility potions in the "right" order
|
||||
if (!dt
|
||||
&& (effect.mEffectId == ESM::MagicEffect::Recall
|
||||
|| effect.mEffectId == ESM::MagicEffect::DivineIntervention
|
||||
|| effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention))
|
||||
|| effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention
|
||||
|| effect.mEffectId == ESM::MagicEffect::Invisibility))
|
||||
return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth };
|
||||
auto& stats = target.getClass().getCreatureStats(target);
|
||||
auto& magnitudes = stats.getMagicEffects();
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
namespace MWPhysics
|
||||
{
|
||||
// https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection
|
||||
bool testAabbAgainstSphere(
|
||||
inline bool testAabbAgainstSphere(
|
||||
const btVector3& aabbMin, const btVector3& aabbMax, const btVector3& position, const btScalar radius)
|
||||
{
|
||||
const btVector3 nearest(std::clamp(position.x(), aabbMin.x(), aabbMax.x()),
|
||||
|
@ -18,35 +18,28 @@ namespace MWPhysics
|
|||
return nearest.distance(position) < radius;
|
||||
}
|
||||
|
||||
template <class Ignore, class OnCollision>
|
||||
class HasSphereCollisionCallback final : public btBroadphaseAabbCallback
|
||||
{
|
||||
public:
|
||||
HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask, const int group,
|
||||
const Ignore& ignore, OnCollision* onCollision)
|
||||
explicit HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask,
|
||||
const int group, const btCollisionObject* ignore)
|
||||
: mPosition(position)
|
||||
, mRadius(radius)
|
||||
, mIgnore(ignore)
|
||||
, mCollisionFilterMask(mask)
|
||||
, mCollisionFilterGroup(group)
|
||||
, mOnCollision(onCollision)
|
||||
{
|
||||
}
|
||||
|
||||
bool process(const btBroadphaseProxy* proxy) override
|
||||
{
|
||||
if (mResult && mOnCollision == nullptr)
|
||||
if (mResult)
|
||||
return false;
|
||||
const auto collisionObject = static_cast<btCollisionObject*>(proxy->m_clientObject);
|
||||
if (mIgnore(collisionObject) || !needsCollision(*proxy)
|
||||
if (mIgnore == collisionObject || !needsCollision(*proxy)
|
||||
|| !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius))
|
||||
return true;
|
||||
mResult = true;
|
||||
if (mOnCollision != nullptr)
|
||||
{
|
||||
(*mOnCollision)(collisionObject);
|
||||
return true;
|
||||
}
|
||||
return !mResult;
|
||||
}
|
||||
|
||||
|
@ -55,10 +48,9 @@ namespace MWPhysics
|
|||
private:
|
||||
btVector3 mPosition;
|
||||
btScalar mRadius;
|
||||
Ignore mIgnore;
|
||||
const btCollisionObject* mIgnore;
|
||||
int mCollisionFilterMask;
|
||||
int mCollisionFilterGroup;
|
||||
OnCollision* mOnCollision;
|
||||
bool mResult = false;
|
||||
|
||||
bool needsCollision(const btBroadphaseProxy& proxy) const
|
||||
|
|
|
@ -849,36 +849,18 @@ namespace MWPhysics
|
|||
mWaterCollisionObject.get(), CollisionType_Water, CollisionType_Actor | CollisionType_Projectile);
|
||||
}
|
||||
|
||||
bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const
|
||||
bool PhysicsSystem::isAreaOccupiedByOtherActor(
|
||||
const MWWorld::LiveCellRefBase* actor, const osg::Vec3f& position, const float radius) const
|
||||
{
|
||||
std::vector<const btCollisionObject*> ignoredObjects;
|
||||
ignoredObjects.reserve(ignore.size());
|
||||
for (const auto& v : ignore)
|
||||
if (const auto it = mActors.find(v.mRef); it != mActors.end())
|
||||
ignoredObjects.push_back(it->second->getCollisionObject());
|
||||
std::sort(ignoredObjects.begin(), ignoredObjects.end());
|
||||
ignoredObjects.erase(std::unique(ignoredObjects.begin(), ignoredObjects.end()), ignoredObjects.end());
|
||||
const auto ignoreFilter = [&](const btCollisionObject* v) {
|
||||
return std::binary_search(ignoredObjects.begin(), ignoredObjects.end(), v);
|
||||
};
|
||||
const auto bulletPosition = Misc::Convert::toBullet(position);
|
||||
const auto aabbMin = bulletPosition - btVector3(radius, radius, radius);
|
||||
const auto aabbMax = bulletPosition + btVector3(radius, radius, radius);
|
||||
const btCollisionObject* ignoredObject = nullptr;
|
||||
if (const auto it = mActors.find(actor); it != mActors.end())
|
||||
ignoredObject = it->second->getCollisionObject();
|
||||
const btVector3 bulletPosition = Misc::Convert::toBullet(position);
|
||||
const btVector3 aabbMin = bulletPosition - btVector3(radius, radius, radius);
|
||||
const btVector3 aabbMax = bulletPosition + btVector3(radius, radius, radius);
|
||||
const int mask = MWPhysics::CollisionType_Actor;
|
||||
const int group = MWPhysics::CollisionType_AnyPhysical;
|
||||
if (occupyingActors == nullptr)
|
||||
{
|
||||
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter,
|
||||
static_cast<void (*)(const btCollisionObject*)>(nullptr));
|
||||
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
|
||||
return callback.getResult();
|
||||
}
|
||||
const auto onCollision = [&](const btCollisionObject* object) {
|
||||
if (PtrHolder* holder = static_cast<PtrHolder*>(object->getUserPointer()))
|
||||
occupyingActors->push_back(holder->getPtr());
|
||||
};
|
||||
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, &onCollision);
|
||||
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoredObject);
|
||||
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
|
||||
return callback.getResult();
|
||||
}
|
||||
|
|
|
@ -281,8 +281,8 @@ namespace MWPhysics
|
|||
std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function);
|
||||
}
|
||||
|
||||
bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const;
|
||||
bool isAreaOccupiedByOtherActor(
|
||||
const MWWorld::LiveCellRefBase* actor, const osg::Vec3f& position, float radius) const;
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||
void reportCollision(const btVector3& position, const btVector3& normal);
|
||||
|
|
|
@ -566,10 +566,10 @@ namespace MWWorld
|
|||
std::vector<Ref> refs;
|
||||
std::set<ESM::RefId> keyIDs;
|
||||
std::vector<ESM::RefId> refIDs;
|
||||
Store<ESM::Cell> Cells = get<ESM::Cell>();
|
||||
for (auto it = Cells.intBegin(); it != Cells.intEnd(); ++it)
|
||||
const Store<ESM::Cell>& cells = get<ESM::Cell>();
|
||||
for (auto it = cells.intBegin(); it != cells.intEnd(); ++it)
|
||||
readRefs(*it, refs, refIDs, keyIDs, readers);
|
||||
for (auto it = Cells.extBegin(); it != Cells.extEnd(); ++it)
|
||||
for (auto it = cells.extBegin(); it != cells.extEnd(); ++it)
|
||||
readRefs(*it, refs, refIDs, keyIDs, readers);
|
||||
const auto lessByRefNum = [](const Ref& l, const Ref& r) { return l.mRefNum < r.mRefNum; };
|
||||
std::stable_sort(refs.begin(), refs.end(), lessByRefNum);
|
||||
|
|
|
@ -3871,10 +3871,11 @@ namespace MWWorld
|
|||
return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal);
|
||||
}
|
||||
|
||||
bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const
|
||||
bool World::isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const
|
||||
{
|
||||
return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore, occupyingActors);
|
||||
const osg::Vec3f halfExtents = getPathfindingAgentBounds(actor).mHalfExtents;
|
||||
const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
|
||||
return mPhysics->isAreaOccupiedByOtherActor(actor.mRef, position, 2 * maxHalfExtent);
|
||||
}
|
||||
|
||||
void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
||||
|
|
|
@ -664,8 +664,7 @@ namespace MWWorld
|
|||
bool hasCollisionWithDoor(
|
||||
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override;
|
||||
|
||||
bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const override;
|
||||
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const override;
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <components/esm3/loadnpc.hpp>
|
||||
#include <components/esm3/readerscache.hpp>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace MWWorld
|
||||
|
@ -36,7 +37,7 @@ namespace MWWorld
|
|||
LiveCellRef<ESM::NPC> liveCellRef(cellRef, &npc);
|
||||
liveCellRef.mData.setDeletedByContentFile(true);
|
||||
Ptr ptr(&liveCellRef);
|
||||
EXPECT_EQ(ptr.toString(), "deleted object0xd00002a (NPC, \"player\")");
|
||||
EXPECT_THAT(ptr.toString(), StrCaseEq("deleted object0xd00002a (NPC, \"player\")"));
|
||||
}
|
||||
|
||||
TEST(MWWorldPtrTest, toStringShouldReturnHumanReadableTextRepresentationOfPtr)
|
||||
|
@ -53,7 +54,7 @@ namespace MWWorld
|
|||
cellRef.mRefNum = ESM::RefNum{ .mIndex = 0x2a, .mContentFile = 0xd };
|
||||
LiveCellRef<ESM::NPC> liveCellRef(cellRef, &npc);
|
||||
Ptr ptr(&liveCellRef);
|
||||
EXPECT_EQ(ptr.toString(), "object0xd00002a (NPC, \"player\")");
|
||||
EXPECT_THAT(ptr.toString(), StrCaseEq("object0xd00002a (NPC, \"player\")"));
|
||||
}
|
||||
|
||||
TEST(MWWorldPtrTest, underlyingLiveCellRefShouldBeDeregisteredOnDestruction)
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
|
@ -64,6 +63,25 @@ namespace DetourNavigator
|
|||
std::reference_wrapper<const RecastSettings> mSettings;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class ToNavMeshCoordinatesSpan
|
||||
{
|
||||
public:
|
||||
explicit ToNavMeshCoordinatesSpan(std::span<T> span, const RecastSettings& settings)
|
||||
: mSpan(span)
|
||||
, mSettings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
std::size_t size() const noexcept { return mSpan.size(); }
|
||||
|
||||
T operator[](std::size_t i) const noexcept { return toNavMeshCoordinates(mSettings, mSpan[i]); }
|
||||
|
||||
private:
|
||||
std::span<T> mSpan;
|
||||
const RecastSettings& mSettings;
|
||||
};
|
||||
|
||||
inline std::optional<std::size_t> findPolygonPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef,
|
||||
const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter,
|
||||
std::span<dtPolyRef> pathBuffer)
|
||||
|
@ -79,7 +97,7 @@ namespace DetourNavigator
|
|||
}
|
||||
|
||||
Status makeSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& start, const osg::Vec3f& end,
|
||||
std::span<dtPolyRef> polygonPath, std::size_t polygonPathSize, std::size_t maxSmoothPathSize,
|
||||
std::span<dtPolyRef> polygonPath, std::size_t polygonPathSize, std::size_t maxSmoothPathSize, bool skipFirst,
|
||||
std::output_iterator<osg::Vec3f> auto& out)
|
||||
{
|
||||
assert(polygonPathSize <= polygonPath.size());
|
||||
|
@ -95,7 +113,7 @@ namespace DetourNavigator
|
|||
dtStatusFailed(status))
|
||||
return Status::FindStraightPathFailed;
|
||||
|
||||
for (int i = 0; i < cornersCount; ++i)
|
||||
for (int i = skipFirst ? 1 : 0; i < cornersCount; ++i)
|
||||
*out++ = Misc::Convert::makeOsgVec3f(&cornerVertsBuffer[static_cast<std::size_t>(i) * 3]);
|
||||
|
||||
return Status::Success;
|
||||
|
@ -103,7 +121,8 @@ namespace DetourNavigator
|
|||
|
||||
Status findSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& halfExtents, const osg::Vec3f& start,
|
||||
const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, const DetourSettings& settings,
|
||||
float endTolerance, std::output_iterator<osg::Vec3f> auto out)
|
||||
float endTolerance, const ToNavMeshCoordinatesSpan<const osg::Vec3f>& checkpoints,
|
||||
std::output_iterator<osg::Vec3f> auto out)
|
||||
{
|
||||
dtQueryFilter queryFilter;
|
||||
queryFilter.setIncludeFlags(includeFlags);
|
||||
|
@ -131,29 +150,66 @@ namespace DetourNavigator
|
|||
return Status::EndPolygonNotFound;
|
||||
|
||||
std::vector<dtPolyRef> polygonPath(settings.mMaxPolygonPathSize);
|
||||
const auto polygonPathSize
|
||||
= findPolygonPath(navMeshQuery, startRef, endRef, startNavMeshPos, endNavMeshPos, queryFilter, polygonPath);
|
||||
std::span<dtPolyRef> polygonPathBuffer = polygonPath;
|
||||
dtPolyRef currentRef = startRef;
|
||||
osg::Vec3f currentNavMeshPos = startNavMeshPos;
|
||||
bool skipFirst = false;
|
||||
|
||||
if (!polygonPathSize.has_value())
|
||||
for (std::size_t i = 0; i < checkpoints.size(); ++i)
|
||||
{
|
||||
const osg::Vec3f checkpointPos = checkpoints[i];
|
||||
osg::Vec3f checkpointNavMeshPos;
|
||||
dtPolyRef checkpointRef;
|
||||
if (const dtStatus status = navMeshQuery.findNearestPoly(checkpointPos.ptr(), polyHalfExtents.ptr(),
|
||||
&queryFilter, &checkpointRef, checkpointNavMeshPos.ptr());
|
||||
dtStatusFailed(status) || checkpointRef == 0)
|
||||
continue;
|
||||
|
||||
const std::optional<std::size_t> toCheckpointPathSize = findPolygonPath(navMeshQuery, currentRef,
|
||||
checkpointRef, currentNavMeshPos, checkpointNavMeshPos, queryFilter, polygonPath);
|
||||
|
||||
if (!toCheckpointPathSize.has_value())
|
||||
continue;
|
||||
|
||||
if (*toCheckpointPathSize == 0)
|
||||
continue;
|
||||
|
||||
if (polygonPath[*toCheckpointPathSize - 1] != checkpointRef)
|
||||
continue;
|
||||
|
||||
const Status smoothStatus = makeSmoothPath(navMeshQuery, currentNavMeshPos, checkpointNavMeshPos,
|
||||
polygonPath, *toCheckpointPathSize, settings.mMaxSmoothPathSize, skipFirst, out);
|
||||
|
||||
if (smoothStatus != Status::Success)
|
||||
return smoothStatus;
|
||||
|
||||
currentRef = checkpointRef;
|
||||
currentNavMeshPos = checkpointNavMeshPos;
|
||||
skipFirst = true;
|
||||
}
|
||||
|
||||
const std::optional<std::size_t> toEndPathSize = findPolygonPath(
|
||||
navMeshQuery, currentRef, endRef, currentNavMeshPos, endNavMeshPos, queryFilter, polygonPathBuffer);
|
||||
|
||||
if (!toEndPathSize.has_value())
|
||||
return Status::FindPathOverPolygonsFailed;
|
||||
|
||||
if (*polygonPathSize == 0)
|
||||
return Status::Success;
|
||||
if (*toEndPathSize == 0)
|
||||
return currentRef == endRef ? Status::Success : Status::PartialPath;
|
||||
|
||||
osg::Vec3f targetNavMeshPos;
|
||||
if (const dtStatus status = navMeshQuery.closestPointOnPoly(
|
||||
polygonPath[*polygonPathSize - 1], end.ptr(), targetNavMeshPos.ptr(), nullptr);
|
||||
polygonPath[*toEndPathSize - 1], end.ptr(), targetNavMeshPos.ptr(), nullptr);
|
||||
dtStatusFailed(status))
|
||||
return Status::TargetPolygonNotFound;
|
||||
|
||||
const bool partialPath = polygonPath[*polygonPathSize - 1] != endRef;
|
||||
const Status smoothStatus = makeSmoothPath(navMeshQuery, startNavMeshPos, targetNavMeshPos, polygonPath,
|
||||
*polygonPathSize, settings.mMaxSmoothPathSize, out);
|
||||
const Status smoothStatus = makeSmoothPath(navMeshQuery, currentNavMeshPos, targetNavMeshPos, polygonPath,
|
||||
*toEndPathSize, settings.mMaxSmoothPathSize, skipFirst, out);
|
||||
|
||||
if (smoothStatus != Status::Success)
|
||||
return smoothStatus;
|
||||
|
||||
return partialPath ? Status::PartialPath : Status::Success;
|
||||
return polygonPath[*toEndPathSize - 1] == endRef ? Status::Success : Status::PartialPath;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
|
@ -21,13 +22,13 @@ namespace DetourNavigator
|
|||
* @param end path at given point.
|
||||
* @param includeFlags setup allowed navmesh areas.
|
||||
* @param out the beginning of the destination range.
|
||||
* @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents
|
||||
* @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents.
|
||||
* @param checkpoints is a sequence of positions the path should go over if possible.
|
||||
* @return Status.
|
||||
* Equal to out if no path is found.
|
||||
*/
|
||||
inline Status findPath(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start,
|
||||
const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, float endTolerance,
|
||||
std::output_iterator<osg::Vec3f> auto out)
|
||||
std::span<const osg::Vec3f> checkpoints, std::output_iterator<osg::Vec3f> auto out)
|
||||
{
|
||||
const auto navMesh = navigator.getNavMesh(agentBounds);
|
||||
if (navMesh == nullptr)
|
||||
|
@ -37,7 +38,8 @@ namespace DetourNavigator
|
|||
const auto locked = navMesh->lock();
|
||||
return findSmoothPath(locked->getQuery(), toNavMeshCoordinates(settings.mRecast, agentBounds.mHalfExtents),
|
||||
toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, end), includeFlags,
|
||||
areaCosts, settings.mDetour, endTolerance, outTransform);
|
||||
areaCosts, settings.mDetour, endTolerance, ToNavMeshCoordinatesSpan(checkpoints, settings.mRecast),
|
||||
outTransform);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,9 @@ namespace ESMTerrain
|
|||
inline std::pair<std::size_t, std::size_t> toCellAndLocal(
|
||||
std::size_t begin, std::size_t global, std::size_t cellSize)
|
||||
{
|
||||
// NOLINTBEGIN(clang-analyzer-core.UndefinedBinaryOperatorResult)
|
||||
std::size_t cell = global / (cellSize - 1);
|
||||
// NOLINTEND(clang-analyzer-core.UndefinedBinaryOperatorResult)
|
||||
std::size_t local = global & (cellSize - 2);
|
||||
if (global != begin && local == 0)
|
||||
{
|
||||
|
|
|
@ -59,10 +59,18 @@ namespace Misc
|
|||
point.y() -= static_cast<float>(mCellY);
|
||||
}
|
||||
|
||||
osg::Vec3f toWorldVec3(const osg::Vec3f& point) const
|
||||
{
|
||||
osg::Vec3f result = point;
|
||||
toWorld(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
osg::Vec3f toLocalVec3(const osg::Vec3f& point) const
|
||||
{
|
||||
return osg::Vec3f(
|
||||
point.x() - static_cast<float>(mCellX), point.y() - static_cast<float>(mCellY), point.z());
|
||||
osg::Vec3f result = point;
|
||||
toLocal(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
53
components/misc/pathgridutils.hpp
Normal file
53
components/misc/pathgridutils.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#ifndef OPENMW_COMPONENTS_MISC_PATHGRIDUTILS_H
|
||||
#define OPENMW_COMPONENTS_MISC_PATHGRIDUTILS_H
|
||||
|
||||
#include "convert.hpp"
|
||||
|
||||
#include <components/esm3/loadpgrd.hpp>
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Misc
|
||||
{
|
||||
// Slightly cheaper version for comparisons.
|
||||
// Caller needs to be careful for very short distances (i.e. less than 1)
|
||||
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
|
||||
//
|
||||
inline float distanceSquared(const ESM::Pathgrid::Point& point, const osg::Vec3f& pos)
|
||||
{
|
||||
return (Misc::Convert::makeOsgVec3f(point) - pos).length2();
|
||||
}
|
||||
|
||||
// Return the closest pathgrid point index from the specified position
|
||||
// coordinates. NOTE: Does not check if there is a sensible way to get there
|
||||
// (e.g. a cliff in front).
|
||||
//
|
||||
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
|
||||
//
|
||||
inline std::size_t getClosestPoint(const ESM::Pathgrid& grid, const osg::Vec3f& pos)
|
||||
{
|
||||
if (grid.mPoints.empty())
|
||||
throw std::invalid_argument("Pathgrid has no points");
|
||||
|
||||
float minDistance = distanceSquared(grid.mPoints[0], pos);
|
||||
std::size_t closestIndex = 0;
|
||||
|
||||
// TODO: if this full scan causes performance problems mapping pathgrid
|
||||
// points to a quadtree may help
|
||||
for (std::size_t i = 1; i < grid.mPoints.size(); ++i)
|
||||
{
|
||||
const float distance = distanceSquared(grid.mPoints[i], pos);
|
||||
if (minDistance > distance)
|
||||
{
|
||||
minDistance = distance;
|
||||
closestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return closestIndex;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -229,7 +229,7 @@
|
|||
-- * `boneName` - name of the bone to attach the vfx to. (default: "")
|
||||
-- * `particleTextureOverride` - name of the particle texture to use. (default: "")
|
||||
-- * `vfxId` - a string ID that can be used to remove the effect later, using #removeVfx, and to avoid duplicate effects. The default value of "" can have duplicates. To avoid interaction with the engine, use unique identifiers unrelated to magic effect IDs. The engine uses this identifier to add and remove magic effects based on what effects are active on the actor. If this is set equal to the @{openmw.core#MagicEffectId} identifier of the magic effect being added, for example core.magic.EFFECT_TYPE.FireDamage, then the engine will remove it once the fire damage effect on the actor reaches 0. (Default: "").
|
||||
-- * `useAmbientLighting` - boolean, vfx get a white ambient light attached in Morrowind. If false don't attach this. (default: true)
|
||||
-- * `useAmbientLight` - boolean, vfx get a white ambient light attached in Morrowind. If false don't attach this. (default: true)
|
||||
--
|
||||
-- @usage local mgef = core.magic.effects.records[myEffectName]
|
||||
-- anim.addVfx(self, 'VFX_Hands', {boneName = 'Bip01 L Hand', particleTextureOverride = mgef.particle, loop = mgef.continuousVfx, vfxId = mgef.id..'_myuniquenamehere'})
|
||||
|
|
|
@ -180,6 +180,7 @@
|
|||
-- @field [parent=#FindPathOptions] #AreaCosts areaCosts a table defining relative cost for each type of area.
|
||||
-- @field [parent=#FindPathOptions] #number destinationTolerance a floating point number representing maximum allowed
|
||||
-- distance between destination and a nearest point on the navigation mesh in addition to agent size (default: 1).
|
||||
-- @field [parent=#FindPathOptions] #table checkpoints an array of positions to build path over if possible.
|
||||
|
||||
---
|
||||
-- A table of parameters for @{#nearby.findRandomPointAroundCircle} and @{#nearby.castNavigationRay}
|
||||
|
|
|
@ -195,7 +195,7 @@
|
|||
-- * `mwMagicVfx` - Boolean that if true causes the textureOverride parameter to only affect nodes with the Nif::RC_NiTexturingProperty property set. (default: true).
|
||||
-- * `particleTextureOverride` - Name of a particle texture that should override this effect's default texture. (default: "")
|
||||
-- * `scale` - A number that scales the size of the vfx (Default: 1)
|
||||
-- * `useAmbientLighting` - boolean, vfx get a white ambient light attached in Morrowind. If false don't attach this. (default: 1)
|
||||
-- * `useAmbientLight` - boolean, vfx get a white ambient light attached in Morrowind. If false don't attach this. (default: true)
|
||||
--
|
||||
-- @usage -- Spawn a sanctuary effect near the player
|
||||
-- local effect = core.magic.effects.records[core.magic.EFFECT_TYPE.Sanctuary]
|
||||
|
|
|
@ -315,6 +315,7 @@ registerPlayerTest('player rotation')
|
|||
registerPlayerTest('player forward running')
|
||||
registerPlayerTest('player diagonal walking')
|
||||
registerPlayerTest('findPath')
|
||||
registerPlayerTest('findPath with checkpoints')
|
||||
registerPlayerTest('findRandomPointAroundCircle')
|
||||
registerPlayerTest('castNavigationRay')
|
||||
registerPlayerTest('findNearestNavMeshPosition')
|
||||
|
|
|
@ -82,6 +82,7 @@ registerGlobalTest('player rotation', 'rotating player should not lead to nan ro
|
|||
registerGlobalTest('player forward running')
|
||||
registerGlobalTest('player diagonal walking')
|
||||
registerGlobalTest('findPath')
|
||||
registerGlobalTest('findPath with checkpoints')
|
||||
registerGlobalTest('findRandomPointAroundCircle')
|
||||
registerGlobalTest('castNavigationRay')
|
||||
registerGlobalTest('findNearestNavMeshPosition')
|
||||
|
|
|
@ -179,6 +179,39 @@ testing.registerLocalTest('findPath',
|
|||
testing.expectEqual(status, nearby.FIND_PATH_STATUS.Success, 'Status')
|
||||
testing.expectLessOrEqual((path[#path] - dst):length(), 1,
|
||||
'Last path point ' .. testing.formatActualExpected(path[#path], dst))
|
||||
testing.expectThat(path, matchers.equalTo({
|
||||
matchers.closeToVector(util.vector3(4096, 4096, 1746.27099609375), 1e-1),
|
||||
matchers.closeToVector(util.vector3(4500, 4500, 1745.95263671875), 1e-1),
|
||||
}))
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('findPath with checkpoints',
|
||||
function()
|
||||
local src = util.vector3(4096, 4096, 1745)
|
||||
local dst = util.vector3(4500, 4500, 1745.95263671875)
|
||||
local options = {
|
||||
agentBounds = types.Actor.getPathfindingAgentBounds(self),
|
||||
includeFlags = nearby.NAVIGATOR_FLAGS.Walk + nearby.NAVIGATOR_FLAGS.Swim,
|
||||
areaCosts = {
|
||||
water = 1,
|
||||
door = 2,
|
||||
ground = 1,
|
||||
pathgrid = 1,
|
||||
},
|
||||
destinationTolerance = 1,
|
||||
checkpoints = {
|
||||
util.vector3(4200, 4100, 1750),
|
||||
},
|
||||
}
|
||||
local status, path = nearby.findPath(src, dst, options)
|
||||
testing.expectEqual(status, nearby.FIND_PATH_STATUS.Success, 'Status')
|
||||
testing.expectLessOrEqual((path[#path] - dst):length(), 1,
|
||||
'Last path point ' .. testing.formatActualExpected(path[#path], dst))
|
||||
testing.expectThat(path, matchers.equalTo({
|
||||
matchers.closeToVector(util.vector3(4096, 4096, 1746.27099609375), 1e-1),
|
||||
matchers.closeToVector(util.vector3(4200, 4100, 1749.5076904296875), 1e-1),
|
||||
matchers.closeToVector(util.vector3(4500, 4500, 1745.95263671875), 1e-1),
|
||||
}))
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('findRandomPointAroundCircle',
|
||||
|
|
Loading…
Reference in a new issue