mirror of
https://github.com/OpenMW/openmw.git
synced 2025-11-29 15:04:39 +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: >
|
Checks: >
|
||||||
-*,
|
-*,
|
||||||
portability-*,
|
portability-*,
|
||||||
|
-portability-template-virtual-member-function,
|
||||||
clang-analyzer-*,
|
clang-analyzer-*,
|
||||||
-clang-analyzer-optin.*,
|
-clang-analyzer-optin.*,
|
||||||
-clang-analyzer-cplusplus.NewDeleteLeaks,
|
-clang-analyzer-cplusplus.NewDeleteLeaks,
|
||||||
|
|
@ -16,4 +17,4 @@ CheckOptions:
|
||||||
- key: readability-identifier-naming.NamespaceCase
|
- key: readability-identifier-naming.NamespaceCase
|
||||||
value: CamelCase
|
value: CamelCase
|
||||||
- key: readability-identifier-naming.NamespaceIgnoredRegexp
|
- 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)
|
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);
|
Status::NavMeshNotFound);
|
||||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>());
|
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)
|
TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception)
|
||||||
{
|
{
|
||||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
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);
|
Status::StartPolygonNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,7 +156,7 @@ namespace
|
||||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||||
mNavigator->removeAgent(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);
|
Status::StartPolygonNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,7 +172,7 @@ namespace
|
||||||
updateGuard.reset();
|
updateGuard.reset();
|
||||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -194,7 +194,7 @@ namespace
|
||||||
updateGuard.reset();
|
updateGuard.reset();
|
||||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath, ElementsAre(Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125))) << mPath;
|
EXPECT_THAT(mPath, ElementsAre(Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125))) << mPath;
|
||||||
|
|
@ -218,7 +218,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -237,7 +237,7 @@ namespace
|
||||||
|
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
mOut = std::back_inserter(mPath);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -265,7 +265,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -285,7 +285,7 @@ namespace
|
||||||
|
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
mOut = std::back_inserter(mPath);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -318,7 +318,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -386,7 +386,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -421,7 +421,7 @@ namespace
|
||||||
mEnd.x() = 256;
|
mEnd.x() = 256;
|
||||||
mEnd.z() = 300;
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -453,8 +453,8 @@ namespace
|
||||||
mStart.x() = 256;
|
mStart.x() = 256;
|
||||||
mEnd.x() = 256;
|
mEnd.x() = 256;
|
||||||
|
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance,
|
||||||
findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
{}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -487,8 +487,8 @@ namespace
|
||||||
mStart.x() = 256;
|
mStart.x() = 256;
|
||||||
mEnd.x() = 256;
|
mEnd.x() = 256;
|
||||||
|
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance,
|
||||||
findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
{}, mOut),
|
||||||
Status::Success);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -520,7 +520,7 @@ namespace
|
||||||
mStart.x() = 256;
|
mStart.x() = 256;
|
||||||
mEnd.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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -549,7 +549,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -577,7 +577,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -658,7 +658,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -781,7 +781,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -806,7 +806,7 @@ namespace
|
||||||
mNavigator->update(mPlayerPosition, nullptr);
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
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);
|
Status::PartialPath);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -834,7 +834,7 @@ namespace
|
||||||
|
|
||||||
const float endTolerance = 1000.0f;
|
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);
|
Status::Success);
|
||||||
|
|
||||||
EXPECT_THAT(mPath,
|
EXPECT_THAT(mPath,
|
||||||
|
|
@ -979,6 +979,146 @@ namespace
|
||||||
EXPECT_EQ(usedNavMeshTiles, 854);
|
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>
|
struct DetourNavigatorNavigatorNotSupportedAgentBoundsTest : TestWithParam<AgentBounds>
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace sfs = std::filesystem;
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// from configfileparser.cpp
|
// from configfileparser.cpp
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
#include <components/files/conversion.hpp>
|
#include <components/files/conversion.hpp>
|
||||||
|
|
||||||
namespace bpo = boost::program_options;
|
namespace bpo = boost::program_options;
|
||||||
namespace sfs = std::filesystem;
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
|
|
|
||||||
|
|
@ -589,8 +589,7 @@ namespace MWBase
|
||||||
virtual bool hasCollisionWithDoor(
|
virtual bool hasCollisionWithDoor(
|
||||||
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0;
|
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0;
|
||||||
|
|
||||||
virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
virtual bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const = 0;
|
||||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors = nullptr) const = 0;
|
|
||||||
|
|
||||||
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
|
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,7 @@ namespace MWLua
|
||||||
DetourNavigator::Flags includeFlags = defaultIncludeFlags;
|
DetourNavigator::Flags includeFlags = defaultIncludeFlags;
|
||||||
DetourNavigator::AreaCosts areaCosts{};
|
DetourNavigator::AreaCosts areaCosts{};
|
||||||
float destinationTolerance = 1;
|
float destinationTolerance = 1;
|
||||||
|
std::vector<osg::Vec3f> checkpoints;
|
||||||
|
|
||||||
if (options.has_value())
|
if (options.has_value())
|
||||||
{
|
{
|
||||||
|
|
@ -258,13 +259,24 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
if (const auto& v = options->get<sol::optional<float>>("destinationTolerance"))
|
if (const auto& v = options->get<sol::optional<float>>("destinationTolerance"))
|
||||||
destinationTolerance = *v;
|
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;
|
std::vector<osg::Vec3f> path;
|
||||||
|
|
||||||
const DetourNavigator::Status status
|
const DetourNavigator::Status status = DetourNavigator::findPath(
|
||||||
= DetourNavigator::findPath(*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds,
|
*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, source, destination,
|
||||||
source, destination, includeFlags, areaCosts, destinationTolerance, std::back_inserter(path));
|
includeFlags, areaCosts, destinationTolerance, checkpoints, std::back_inserter(path));
|
||||||
|
|
||||||
sol::table result(lua, sol::create);
|
sol::table result(lua, sol::create);
|
||||||
LuaUtil::copyVectorToTable(path, result);
|
LuaUtil::copyVectorToTable(path, result);
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,21 @@ namespace
|
||||||
|
|
||||||
namespace MWMechanics
|
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)
|
ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells)
|
||||||
: mActiveSpells(spells)
|
: mActiveSpells(spells)
|
||||||
{
|
{
|
||||||
|
|
@ -256,8 +271,9 @@ namespace MWMechanics
|
||||||
++spellIt;
|
++spellIt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateContext context(duration > 0.f);
|
||||||
for (const auto& spell : mQueue)
|
for (const auto& spell : mQueue)
|
||||||
addToSpells(ptr, spell);
|
addToSpells(ptr, spell, context);
|
||||||
mQueue.clear();
|
mQueue.clear();
|
||||||
|
|
||||||
// Vanilla only does this on cell change I think
|
// 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
|
if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power
|
||||||
&& !isSpellActive(spell->mId))
|
&& !isSpellActive(spell->mId))
|
||||||
{
|
{
|
||||||
mSpells.emplace_back(ActiveSpellParams{ spell, ptr, true });
|
initParams(ptr, ActiveSpellParams{ spell, ptr, true }, context);
|
||||||
mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool updateSpellWindow = false;
|
|
||||||
bool playNonLooping = false;
|
|
||||||
if (ptr.getClass().hasInventoryStore(ptr)
|
if (ptr.getClass().hasInventoryStore(ptr)
|
||||||
&& !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()))
|
&& !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()))
|
||||||
{
|
{
|
||||||
auto& store = ptr.getClass().getInventoryStore(ptr);
|
auto& store = ptr.getClass().getInventoryStore(ptr);
|
||||||
if (store.getInvListener() != nullptr)
|
if (store.getInvListener() != nullptr)
|
||||||
{
|
{
|
||||||
playNonLooping = !store.isFirstEquip();
|
context.mPlayNonLooping = !store.isFirstEquip();
|
||||||
const auto world = MWBase::Environment::get().getWorld();
|
const auto world = MWBase::Environment::get().getWorld();
|
||||||
for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++)
|
for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++)
|
||||||
{
|
{
|
||||||
|
|
@ -306,82 +319,108 @@ namespace MWMechanics
|
||||||
// invisibility manually
|
// invisibility manually
|
||||||
purgeEffect(ptr, ESM::MagicEffect::Invisibility);
|
purgeEffect(ptr, ESM::MagicEffect::Invisibility);
|
||||||
applyPurges(ptr);
|
applyPurges(ptr);
|
||||||
ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr });
|
ActiveSpellParams* params = initParams(ptr, ActiveSpellParams{ *slot, enchantment, ptr }, context);
|
||||||
params.setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
if (params)
|
||||||
updateSpellWindow = true;
|
context.mUpdateSpellWindow = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MWWorld::Ptr player = MWMechanics::getPlayer();
|
const MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||||
bool updatedHitOverlay = false;
|
|
||||||
bool updatedEnemy = false;
|
|
||||||
// Update effects
|
// Update effects
|
||||||
|
context.mEraseRemoved = true;
|
||||||
for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
|
for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
|
||||||
{
|
{
|
||||||
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(
|
updateActiveSpell(ptr, duration, spellIt, context);
|
||||||
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;
|
|
||||||
|
|
||||||
|
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;
|
bool remove = false;
|
||||||
if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore))
|
if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
auto& spells = ptr.getClass().getCreatureStats(ptr).getSpells();
|
||||||
remove = !spells.hasSpell(spellIt->mSourceSpellId);
|
remove = !spells.hasSpell(spellIt->mSourceSpellId);
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error& e)
|
catch (const std::runtime_error& e)
|
||||||
|
|
@ -413,30 +452,27 @@ namespace MWMechanics
|
||||||
for (const auto& effect : params.mEffects)
|
for (const auto& effect : params.mEffects)
|
||||||
onMagicEffectRemoved(ptr, params, effect);
|
onMagicEffectRemoved(ptr, params, effect);
|
||||||
applyPurges(ptr, &spellIt);
|
applyPurges(ptr, &spellIt);
|
||||||
updateSpellWindow = true;
|
context.mUpdateSpellWindow = true;
|
||||||
continue;
|
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))
|
if (!spell.hasFlag(ESM::ActiveSpells::Flag_Stackable))
|
||||||
{
|
{
|
||||||
|
|
@ -454,8 +490,7 @@ namespace MWMechanics
|
||||||
onMagicEffectRemoved(ptr, params, effect);
|
onMagicEffectRemoved(ptr, params, effect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mSpells.emplace_back(spell);
|
initParams(ptr, spell, context);
|
||||||
mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveSpells::ActiveSpells()
|
ActiveSpells::ActiveSpells()
|
||||||
|
|
@ -608,6 +643,8 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
purge(
|
purge(
|
||||||
[=](const ActiveSpellParams&, const ESM::ActiveEffect& effect) {
|
[=](const ActiveSpellParams&, const ESM::ActiveEffect& effect) {
|
||||||
|
if (!(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||||
|
return false;
|
||||||
if (effectArg.empty())
|
if (effectArg.empty())
|
||||||
return effect.mEffectId == effectId;
|
return effect.mEffectId == effectId;
|
||||||
return effect.mEffectId == effectId && effect.getSkillOrAttribute() == effectArg;
|
return effect.mEffectId == effectId && effect.getSkillOrAttribute() == effectArg;
|
||||||
|
|
|
||||||
|
|
@ -116,17 +116,23 @@ namespace MWMechanics
|
||||||
IterationGuard(ActiveSpells& spells);
|
IterationGuard(ActiveSpells& spells);
|
||||||
~IterationGuard();
|
~IterationGuard();
|
||||||
};
|
};
|
||||||
|
struct UpdateContext;
|
||||||
|
|
||||||
std::list<ActiveSpellParams> mSpells;
|
std::list<ActiveSpellParams> mSpells;
|
||||||
std::vector<ActiveSpellParams> mQueue;
|
std::vector<ActiveSpellParams> mQueue;
|
||||||
std::queue<Predicate> mPurges;
|
std::queue<Predicate> mPurges;
|
||||||
bool mIterating;
|
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,
|
bool applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell = nullptr,
|
||||||
std::vector<ActiveEffect>::iterator* currentEffect = 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:
|
public:
|
||||||
ActiveSpells();
|
ActiveSpells();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1326,19 +1326,37 @@ namespace MWMechanics
|
||||||
|
|
||||||
const MWWorld::Ptr player = getPlayer();
|
const MWWorld::Ptr player = getPlayer();
|
||||||
const MWBase::World* const world = MWBase::Environment::get().getWorld();
|
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)
|
for (const Actor& actor : mActors)
|
||||||
{
|
{
|
||||||
if (actor.isInvalid())
|
if (actor.isInvalid())
|
||||||
continue;
|
continue;
|
||||||
const MWWorld::Ptr& ptr = actor.getPtr();
|
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)
|
if (ptr == player)
|
||||||
continue; // Don't interfere with player controls.
|
continue; // Don't interfere with player controls.
|
||||||
|
|
||||||
const float maxSpeed = ptr.getClass().getMaxSpeed(ptr);
|
const float maxSpeed = cached.mMaxSpeed;
|
||||||
if (maxSpeed == 0.0)
|
if (maxSpeed == 0.0)
|
||||||
continue; // Can't move, so there is no sense to predict collisions.
|
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 osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]);
|
||||||
const bool isMoving = origMovement.length2() > 0.01;
|
const bool isMoving = origMovement.length2() > 0.01;
|
||||||
if (movement.mPosition[1] < 0)
|
if (movement.mPosition[1] < 0)
|
||||||
|
|
@ -1379,7 +1397,7 @@ namespace MWMechanics
|
||||||
const osg::Vec2f baseSpeed = origMovement * maxSpeed;
|
const osg::Vec2f baseSpeed = origMovement * maxSpeed;
|
||||||
const osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3();
|
const osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3();
|
||||||
const float baseRotZ = ptr.getRefData().getPosition().rot[2];
|
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;
|
const float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding;
|
||||||
|
|
||||||
float timeToCheck = maxTimeToCheck;
|
float timeToCheck = maxTimeToCheck;
|
||||||
|
|
@ -1392,15 +1410,13 @@ namespace MWMechanics
|
||||||
float angleToApproachingActor = 0;
|
float angleToApproachingActor = 0;
|
||||||
|
|
||||||
// Iterate through all other actors and predict collisions.
|
// Iterate through all other actors and predict collisions.
|
||||||
for (const Actor& otherActor : mActors)
|
for (const CacheEntry& otherCached : cache)
|
||||||
{
|
{
|
||||||
if (otherActor.isInvalid())
|
const MWWorld::Ptr& otherPtr = otherCached.mPtr;
|
||||||
continue;
|
|
||||||
const MWWorld::Ptr& otherPtr = otherActor.getPtr();
|
|
||||||
if (otherPtr == ptr || otherPtr == currentTarget)
|
if (otherPtr == ptr || otherPtr == currentTarget)
|
||||||
continue;
|
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::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos;
|
||||||
const osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ);
|
const osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ);
|
||||||
const float dist = deltaPos.length();
|
const float dist = deltaPos.length();
|
||||||
|
|
@ -1413,8 +1429,7 @@ namespace MWMechanics
|
||||||
if (deltaPos.z() > halfExtents.z() * 2 || deltaPos.z() < -otherHalfExtents.z() * 2)
|
if (deltaPos.z() > halfExtents.z() * 2 || deltaPos.z() < -otherHalfExtents.z() * 2)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const osg::Vec3f speed = otherPtr.getClass().getMovementSettings(otherPtr).asVec3()
|
const osg::Vec3f speed = otherCached.mMovement.asVec3() * otherCached.mMaxSpeed;
|
||||||
* otherPtr.getClass().getMaxSpeed(otherPtr);
|
|
||||||
const float rotZ = otherPtr.getRefData().getPosition().rot[2];
|
const float rotZ = otherPtr.getRefData().getPosition().rot[2];
|
||||||
const osg::Vec2f relSpeed
|
const osg::Vec2f relSpeed
|
||||||
= Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed;
|
= Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
#include "aicombat.hpp"
|
#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/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 <components/sceneutil/positionattitudetransform.hpp>
|
||||||
|
|
||||||
#include "../mwphysics/raycasting.hpp"
|
#include "../mwphysics/raycasting.hpp"
|
||||||
|
|
@ -178,11 +176,16 @@ namespace MWMechanics
|
||||||
currentCell = actor.getCell();
|
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;
|
bool forceFlee = false;
|
||||||
if (!canFight(actor, target))
|
if (!canFight(actor, target))
|
||||||
{
|
{
|
||||||
storage.stopAttack();
|
storage.stopAttack();
|
||||||
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
|
stats.setAttackingOrSpell(false);
|
||||||
storage.mActionCooldown = 0.f;
|
storage.mActionCooldown = 0.f;
|
||||||
// Continue combat if target is player or player follower/escorter and an attack has been attempted
|
// Continue combat if target is player or player follower/escorter and an attack has been attempted
|
||||||
const auto& playerFollowersAndEscorters
|
const auto& playerFollowersAndEscorters
|
||||||
|
|
@ -191,18 +194,14 @@ namespace MWMechanics
|
||||||
= (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target)
|
= (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target)
|
||||||
!= playerFollowersAndEscorters.end());
|
!= playerFollowersAndEscorters.end());
|
||||||
if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer)
|
if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer)
|
||||||
&& ((actor.getClass().getCreatureStats(actor).getHitAttemptActorId()
|
&& ((stats.getHitAttemptActorId() == target.getClass().getCreatureStats(target).getActorId())
|
||||||
== target.getClass().getCreatureStats(target).getActorId())
|
|| (target.getClass().getCreatureStats(target).getHitAttemptActorId() == stats.getActorId())))
|
||||||
|| (target.getClass().getCreatureStats(target).getHitAttemptActorId()
|
|
||||||
== actor.getClass().getCreatureStats(actor).getActorId())))
|
|
||||||
forceFlee = true;
|
forceFlee = true;
|
||||||
else // Otherwise end combat
|
else // Otherwise end combat
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MWWorld::Class& actorClass = actor.getClass();
|
stats.setMovementFlag(CreatureStats::Flag_Run, true);
|
||||||
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
|
||||||
|
|
||||||
float& actionCooldown = storage.mActionCooldown;
|
float& actionCooldown = storage.mActionCooldown;
|
||||||
std::unique_ptr<Action>& currentAction = storage.mCurrentAction;
|
std::unique_ptr<Action>& currentAction = storage.mCurrentAction;
|
||||||
|
|
||||||
|
|
@ -302,8 +301,8 @@ namespace MWMechanics
|
||||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||||
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||||
const auto& pathGridGraph = getPathGridGraph(pathgrid);
|
const auto& pathGridGraph = getPathGridGraph(pathgrid);
|
||||||
mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, agentBounds,
|
mPathFinder.buildPath(actor, vActorPos, vTargetPos, pathGridGraph, agentBounds, navigatorFlags, areaCosts,
|
||||||
navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full);
|
storage.mAttackRange, PathType::Full);
|
||||||
|
|
||||||
if (!mPathFinder.isPathConstructed())
|
if (!mPathFinder.isPathConstructed())
|
||||||
{
|
{
|
||||||
|
|
@ -316,8 +315,8 @@ namespace MWMechanics
|
||||||
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
|
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
|
||||||
{
|
{
|
||||||
// If the point is close enough, try to find a path to that point.
|
// If the point is close enough, try to find a path to that point.
|
||||||
mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, agentBounds,
|
mPathFinder.buildPath(actor, vActorPos, *hit, pathGridGraph, agentBounds, navigatorFlags, areaCosts,
|
||||||
navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full);
|
storage.mAttackRange, PathType::Full);
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
{
|
{
|
||||||
// If path to that point is found use it as custom destination.
|
// If path to that point is found use it as custom destination.
|
||||||
|
|
@ -330,7 +329,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
storage.mUseCustomDestination = false;
|
storage.mUseCustomDestination = false;
|
||||||
storage.stopAttack();
|
storage.stopAttack();
|
||||||
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
|
stats.setAttackingOrSpell(false);
|
||||||
currentAction = std::make_unique<ActionFlee>();
|
currentAction = std::make_unique<ActionFlee>();
|
||||||
actionCooldown = currentAction->getActionCooldown();
|
actionCooldown = currentAction->getActionCooldown();
|
||||||
storage.startFleeing();
|
storage.startFleeing();
|
||||||
|
|
@ -393,8 +392,8 @@ namespace MWMechanics
|
||||||
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
||||||
coords.toLocal(localPos);
|
coords.toLocal(localPos);
|
||||||
|
|
||||||
size_t closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos);
|
const std::size_t closestPointIndex = Misc::getClosestPoint(*pathgrid, localPos);
|
||||||
for (size_t i = 0; i < pathgrid->mPoints.size(); i++)
|
for (std::size_t i = 0; i < pathgrid->mPoints.size(); i++)
|
||||||
{
|
{
|
||||||
if (i != closestPointIndex
|
if (i != closestPointIndex
|
||||||
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, i))
|
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, i))
|
||||||
|
|
@ -455,7 +454,8 @@ namespace MWMechanics
|
||||||
float dist
|
float dist
|
||||||
= (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
= (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
||||||
if ((dist > fFleeDistance && !storage.mLOS)
|
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;
|
state = AiCombatStorage::FleeState_Idle;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@
|
||||||
#define GAME_MWMECHANICS_AICOMBAT_H
|
#define GAME_MWMECHANICS_AICOMBAT_H
|
||||||
|
|
||||||
#include "aitemporarybase.hpp"
|
#include "aitemporarybase.hpp"
|
||||||
|
#include "aitimer.hpp"
|
||||||
|
#include "movement.hpp"
|
||||||
#include "typedaipackage.hpp"
|
#include "typedaipackage.hpp"
|
||||||
|
|
||||||
#include "../mwworld/cellstore.hpp" // for Doors
|
#include "../mwworld/cellstore.hpp" // for Doors
|
||||||
|
|
||||||
#include "aitimer.hpp"
|
#include <components/esm3/loadpgrd.hpp>
|
||||||
#include "movement.hpp"
|
|
||||||
|
|
||||||
namespace ESM
|
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());
|
= world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||||
mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(pathgrid),
|
mPathFinder.buildLimitedPath(actor, position, dest, getPathGridGraph(pathgrid), agentBounds,
|
||||||
agentBounds, navigatorFlags, areaCosts, endTolerance, pathType);
|
navigatorFlags, areaCosts, endTolerance, pathType);
|
||||||
mRotateOnTheRunChecks = 3;
|
mRotateOnTheRunChecks = 3;
|
||||||
|
|
||||||
// give priority to go directly on target if there is minimal opportunity
|
// 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;
|
result |= DetourNavigator::Flag_swim;
|
||||||
|
|
||||||
if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0)
|
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)
|
if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander)
|
||||||
result |= DetourNavigator::Flag_openDoor;
|
result |= DetourNavigator::Flag_openDoor;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
struct Cell;
|
struct Cell;
|
||||||
|
struct Pathgrid;
|
||||||
|
|
||||||
namespace AiSequence
|
namespace AiSequence
|
||||||
{
|
{
|
||||||
struct AiSequence;
|
struct AiSequence;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <components/detournavigator/navigatorutils.hpp>
|
#include <components/detournavigator/navigatorutils.hpp>
|
||||||
#include <components/esm3/aisequence.hpp>
|
#include <components/esm3/aisequence.hpp>
|
||||||
#include <components/misc/coordinateconverter.hpp>
|
#include <components/misc/coordinateconverter.hpp>
|
||||||
|
#include <components/misc/pathgridutils.hpp>
|
||||||
#include <components/misc/rng.hpp>
|
#include <components/misc/rng.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
@ -29,17 +30,6 @@
|
||||||
|
|
||||||
namespace MWMechanics
|
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] = {
|
const std::string_view AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
|
||||||
"idle2",
|
"idle2",
|
||||||
"idle3",
|
"idle3",
|
||||||
|
|
@ -53,11 +43,22 @@ namespace MWMechanics
|
||||||
|
|
||||||
namespace
|
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)
|
inline int getCountBeforeReset(const MWWorld::ConstPtr& actor)
|
||||||
{
|
{
|
||||||
if (actor.getClass().isPureWaterCreature(actor) || actor.getClass().isPureFlyingCreature(actor))
|
if (actor.getClass().isPureWaterCreature(actor) || actor.getClass().isPureFlyingCreature(actor))
|
||||||
return 1;
|
return 1;
|
||||||
return COUNT_BEFORE_RESET;
|
return countBeforeReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3f getRandomPointAround(const osg::Vec3f& position, const float distance)
|
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> getInitialIdle(const std::vector<unsigned char>& idle)
|
||||||
{
|
{
|
||||||
std::vector<unsigned char> result(MAX_IDLE_SIZE, 0);
|
std::vector<unsigned char> result(maxIdleSize, 0);
|
||||||
std::copy_n(idle.begin(), std::min(MAX_IDLE_SIZE, idle.size()), result.begin());
|
std::copy_n(idle.begin(), std::min(maxIdleSize, idle.size()), result.begin());
|
||||||
return result;
|
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));
|
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()
|
AiWanderStorage::AiWanderStorage()
|
||||||
|
|
@ -118,9 +145,9 @@ namespace MWMechanics
|
||||||
, mCanWanderAlongPathGrid(true)
|
, mCanWanderAlongPathGrid(true)
|
||||||
, mIdleAnimation(0)
|
, mIdleAnimation(0)
|
||||||
, mBadIdles()
|
, mBadIdles()
|
||||||
, mPopulateAvailableNodes(true)
|
, mPopulateAvailablePositions(true)
|
||||||
, mAllowedNodes()
|
, mAllowedPositions()
|
||||||
, mTrimCurrentNode(false)
|
, mTrimCurrentPosition(false)
|
||||||
, mCheckIdlePositionTimer(0)
|
, mCheckIdlePositionTimer(0)
|
||||||
, mStuckCount(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)
|
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat)
|
||||||
: TypedAiPackage<AiWander>(repeat)
|
: TypedAiPackage<AiWander>(repeat)
|
||||||
, mDistance(std::max(0, distance))
|
, mDistance(static_cast<unsigned>(std::max(0, distance)))
|
||||||
, mDuration(std::max(0, duration))
|
, mDuration(static_cast<unsigned>(std::max(0, duration)))
|
||||||
, mRemainingDuration(duration)
|
, mRemainingDuration(duration)
|
||||||
, mTimeOfDay(timeOfDay)
|
, mTimeOfDay(timeOfDay)
|
||||||
, mIdle(getInitialIdle(idle))
|
, mIdle(getInitialIdle(idle))
|
||||||
|
|
@ -215,20 +242,12 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
const ESM::Pathgrid* pathgrid
|
const ESM::Pathgrid* pathgrid
|
||||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||||
if (mUsePathgrid)
|
const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor);
|
||||||
{
|
constexpr float endTolerance = 0;
|
||||||
mPathFinder.buildPathByPathgrid(
|
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||||
pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid));
|
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||||
}
|
mPathFinder.buildPath(actor, pos.asVec3(), mDestination, getPathGridGraph(pathgrid), agentBounds,
|
||||||
else
|
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, actor.getCell(), getPathGridGraph(pathgrid),
|
|
||||||
agentBounds, navigatorFlags, areaCosts, endTolerance, PathType::Full);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid);
|
storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid);
|
||||||
|
|
@ -259,9 +278,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
||||||
{
|
{
|
||||||
if (mDistance <= 0)
|
|
||||||
storage.mCanWanderAlongPathGrid = false;
|
|
||||||
|
|
||||||
if (isPackageCompleted())
|
if (isPackageCompleted())
|
||||||
{
|
{
|
||||||
stopWalking(actor);
|
stopWalking(actor);
|
||||||
|
|
@ -276,13 +292,15 @@ namespace MWMechanics
|
||||||
mStoredInitialActorPosition = true;
|
mStoredInitialActorPosition = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialization to discover & store allowed node points for this actor.
|
// Initialization to discover & store allowed positions points for this actor.
|
||||||
if (storage.mPopulateAvailableNodes)
|
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)
|
if (canActorMoveByZAxis(actor) && mDistance > 0)
|
||||||
{
|
{
|
||||||
// Typically want to idle for a short time before the next wander
|
// 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,
|
// If the package has a wander distance but no pathgrid is available,
|
||||||
// randomly idle or wander near spawn point
|
// 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
|
// Typically want to idle for a short time before the next wander
|
||||||
if (Misc::Rng::rollDice(100, prng) >= 96)
|
if (Misc::Rng::rollDice(100, prng) >= 96)
|
||||||
|
|
@ -307,7 +325,7 @@ namespace MWMechanics
|
||||||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually)
|
else if (storage.mAllowedPositions.empty() && !storage.mIsWanderingManually)
|
||||||
{
|
{
|
||||||
storage.mCanWanderAlongPathGrid = false;
|
storage.mCanWanderAlongPathGrid = false;
|
||||||
}
|
}
|
||||||
|
|
@ -323,9 +341,9 @@ namespace MWMechanics
|
||||||
// Construct a new path if there isn't one
|
// Construct a new path if there isn't one
|
||||||
if (!mPathFinder.isPathConstructed())
|
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
|
if (storage.mIsWanderingManually && storage.mState == AiWanderStorage::Wander_Walking
|
||||||
&& (mPathFinder.getPathSize() == 0 || isDestinationHidden(actor, mPathFinder.getPath().back())
|
&& (mPathFinder.getPathSize() == 0 || isDestinationHidden(actor, mPathFinder.getPath().back())
|
||||||
|| isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back())))
|
|| world.isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back())))
|
||||||
completeManualWalking(actor, storage);
|
completeManualWalking(actor, storage);
|
||||||
|
|
||||||
return false; // AiWander package not yet completed
|
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
|
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 isWaterCreature = actor.getClass().isPureWaterCreature(actor);
|
||||||
const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor);
|
const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor);
|
||||||
const auto world = MWBase::Environment::get().getWorld();
|
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||||
const auto agentBounds = world->getPathfindingAgentBounds(actor);
|
const auto agentBounds = world.getPathfindingAgentBounds(actor);
|
||||||
const auto navigator = world->getNavigator();
|
const auto navigator = world.getNavigator();
|
||||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
Misc::Rng::Generator& prng = world.getPrng();
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
|
@ -411,7 +429,7 @@ namespace MWMechanics
|
||||||
if (isDestinationHidden(actor, mDestination))
|
if (isDestinationHidden(actor, mDestination))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (isAreaOccupiedByOtherActor(actor, mDestination))
|
if (world.isAreaOccupiedByOtherActor(actor, mDestination))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
constexpr float endTolerance = 0;
|
constexpr float endTolerance = 0;
|
||||||
|
|
@ -491,17 +509,17 @@ namespace MWMechanics
|
||||||
|
|
||||||
void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
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;
|
storage.mCheckIdlePositionTimer += duration;
|
||||||
|
|
||||||
if (storage.mCheckIdlePositionTimer >= IDLE_POSITION_CHECK_INTERVAL && !isStationary())
|
if (storage.mCheckIdlePositionTimer >= idlePositionCheckInterval && !isStationary())
|
||||||
{
|
{
|
||||||
storage.mCheckIdlePositionTimer = 0; // restart timer
|
storage.mCheckIdlePositionTimer = 0; // restart timer
|
||||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f;
|
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.setState(AiWanderStorage::Wander_MoveNow);
|
||||||
storage.mTrimCurrentNode = false; // just in case
|
storage.mTrimCurrentPosition = false; // just in case
|
||||||
return;
|
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();
|
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||||
for (const ESM::Pathgrid::Point& node : storage.mAllowedNodes)
|
const float squaredDistance = distance * distance;
|
||||||
{
|
return std::ranges::find_if(storage.mAllowedPositions, [&](const osg::Vec3& v) {
|
||||||
osg::Vec3f point(node.mX, node.mY, node.mZ);
|
return (actorPos - v).length2() < squaredDistance;
|
||||||
if ((actorPos - point).length2() < distance * distance)
|
}) != storage.mAllowedPositions.end();
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration,
|
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration,
|
||||||
|
|
@ -535,7 +551,7 @@ namespace MWMechanics
|
||||||
// Is there no destination or are we there yet?
|
// Is there no destination or are we there yet?
|
||||||
if ((!mPathFinder.isPathConstructed())
|
if ((!mPathFinder.isPathConstructed())
|
||||||
|| pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, supportedMovementDirections,
|
|| pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, supportedMovementDirections,
|
||||||
DESTINATION_TOLERANCE))
|
destinationTolerance))
|
||||||
{
|
{
|
||||||
stopWalking(actor);
|
stopWalking(actor);
|
||||||
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||||
|
|
@ -586,8 +602,8 @@ namespace MWMechanics
|
||||||
if (proximityToDoor(actor, distance))
|
if (proximityToDoor(actor, distance))
|
||||||
{
|
{
|
||||||
// remove allowed points then select another random destination
|
// remove allowed points then select another random destination
|
||||||
storage.mTrimCurrentNode = true;
|
storage.mTrimCurrentPosition = true;
|
||||||
trimAllowedNodes(storage.mAllowedNodes, mPathFinder);
|
trimAllowedPositions(mPathFinder.getPath(), storage.mAllowedPositions);
|
||||||
mObstacleCheck.clear();
|
mObstacleCheck.clear();
|
||||||
stopWalking(actor);
|
stopWalking(actor);
|
||||||
storage.setState(AiWanderStorage::Wander_MoveNow);
|
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)
|
const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
|
||||||
{
|
{
|
||||||
auto world = MWBase::Environment::get().getWorld();
|
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||||
auto& prng = world->getPrng();
|
Misc::Rng::Generator& prng = world.getPrng();
|
||||||
unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
|
const std::size_t randomAllowedPositionIndex
|
||||||
const ESM::Pathgrid::Point& dest = storage.mAllowedNodes[randNode];
|
= 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();
|
const osg::Vec3f start = actorPos.asVec3();
|
||||||
|
|
||||||
// don't take shortcuts for wandering
|
const MWWorld::Cell& cell = *actor.getCell()->getCell();
|
||||||
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
const ESM::Pathgrid* pathgrid = world.getStore().get<ESM::Pathgrid>().search(cell);
|
||||||
const osg::Vec3f destVec3f = PathFinder::makeOsgVec3(dest);
|
const PathgridGraph& pathgridGraph = getPathGridGraph(pathgrid);
|
||||||
mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), 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;
|
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
|
||||||
mHasDestination = true;
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
// 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
|
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)
|
storage.setState(AiWanderStorage::Wander_Walking);
|
||||||
{
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::stopWalking(const MWWorld::Ptr& actor)
|
void AiWander::stopWalking(const MWWorld::Ptr& actor)
|
||||||
|
|
@ -742,20 +758,20 @@ namespace MWMechanics
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AiWanderStorage& storage = state.get<AiWanderStorage>();
|
AiWanderStorage& storage = state.get<AiWanderStorage>();
|
||||||
if (storage.mPopulateAvailableNodes)
|
if (storage.mPopulateAvailablePositions)
|
||||||
getAllowedNodes(actor, storage);
|
fillAllowedPositions(actor, storage);
|
||||||
|
|
||||||
if (storage.mAllowedNodes.empty())
|
if (storage.mAllowedPositions.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||||
int index = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
|
int index = Misc::Rng::rollDice(storage.mAllowedPositions.size(), prng);
|
||||||
ESM::Pathgrid::Point worldDest = storage.mAllowedNodes[index];
|
const osg::Vec3f worldDest = storage.mAllowedPositions[index];
|
||||||
const Misc::CoordinateConverter converter = Misc::makeCoordinateConverter(*actor.getCell()->getCell());
|
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(
|
const bool isPathGridOccupied
|
||||||
PathFinder::makeOsgVec3(worldDest), 60);
|
= MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(worldDest, 60);
|
||||||
|
|
||||||
// add offset only if the selected pathgrid is occupied by another actor
|
// add offset only if the selected pathgrid is occupied by another actor
|
||||||
if (isPathGridOccupied)
|
if (isPathGridOccupied)
|
||||||
|
|
@ -775,19 +791,17 @@ namespace MWMechanics
|
||||||
const ESM::Pathgrid::Point& connDest = points[randomIndex];
|
const ESM::Pathgrid::Point& connDest = points[randomIndex];
|
||||||
|
|
||||||
// add an offset towards random neighboring node
|
// add an offset towards random neighboring node
|
||||||
osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest);
|
osg::Vec3f dir = Misc::Convert::makeOsgVec3f(connDest) - dest;
|
||||||
float length = dir.length();
|
const float length = dir.length();
|
||||||
dir.normalize();
|
dir.normalize();
|
||||||
|
|
||||||
for (int j = 1; j <= 3; j++)
|
for (int j = 1; j <= 3; j++)
|
||||||
{
|
{
|
||||||
// move for 5-15% towards random neighboring node
|
// move for 5-15% towards random neighboring node
|
||||||
dest
|
dest = dest + dir * (j * 5 * length / 100.f);
|
||||||
= PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
|
|
||||||
worldDest = converter.toWorldPoint(dest);
|
|
||||||
|
|
||||||
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
|
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
|
||||||
PathFinder::makeOsgVec3(worldDest), 60);
|
converter.toWorldVec3(dest), 60);
|
||||||
|
|
||||||
if (!isOccupied)
|
if (!isOccupied)
|
||||||
break;
|
break;
|
||||||
|
|
@ -807,19 +821,18 @@ namespace MWMechanics
|
||||||
|
|
||||||
// place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be
|
// 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.
|
// underground. Adding 20 in adjustPosition() is not enough.
|
||||||
dest.mZ += 60;
|
dest.z() += 60;
|
||||||
|
|
||||||
converter.toWorld(dest);
|
converter.toWorld(dest);
|
||||||
|
|
||||||
state.reset();
|
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, dest);
|
||||||
MWBase::Environment::get().getWorld()->moveObject(actor, pos);
|
|
||||||
actor.getClass().adjustPosition(actor, false);
|
actor.getClass().adjustPosition(actor, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::getNeighbouringNodes(
|
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
|
const ESM::Pathgrid* pathgrid
|
||||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*currentCell->getCell());
|
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*currentCell->getCell());
|
||||||
|
|
@ -827,19 +840,19 @@ namespace MWMechanics
|
||||||
if (pathgrid == nullptr || pathgrid->mPoints.empty())
|
if (pathgrid == nullptr || pathgrid->mPoints.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
size_t index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
|
const size_t index = Misc::getClosestPoint(*pathgrid, dest);
|
||||||
|
|
||||||
getPathGridGraph(pathgrid).getNeighbouringPoints(index, points);
|
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
|
// infrequently used, therefore no benefit in caching it as a member
|
||||||
const MWWorld::CellStore* cellStore = actor.getCell();
|
const MWWorld::CellStore* cellStore = actor.getCell();
|
||||||
const ESM::Pathgrid* pathgrid
|
const ESM::Pathgrid* pathgrid
|
||||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*cellStore->getCell());
|
= 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:
|
// If there is no path this actor doesn't go anywhere. See:
|
||||||
// https://forum.openmw.org/viewtopic.php?t=1556
|
// https://forum.openmw.org/viewtopic.php?t=1556
|
||||||
|
|
@ -860,34 +873,35 @@ namespace MWMechanics
|
||||||
const osg::Vec3f npcPos = converter.toLocalVec3(mInitialActorPosition);
|
const osg::Vec3f npcPos = converter.toLocalVec3(mInitialActorPosition);
|
||||||
|
|
||||||
// Find closest pathgrid point
|
// 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
|
// and if the point is connected to the closest current point
|
||||||
// NOTE: mPoints is in local coordinates
|
// NOTE: mPoints is in local coordinates
|
||||||
size_t pointIndex = 0;
|
size_t pointIndex = 0;
|
||||||
for (size_t counter = 0; counter < pathgrid->mPoints.size(); counter++)
|
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
|
if ((npcPos - nodePos).length2() <= mDistance * mDistance
|
||||||
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, counter))
|
&& 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;
|
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);
|
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,
|
// When only one path grid point in wander distance,
|
||||||
|
|
@ -901,44 +915,44 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
if (edge.mV0 == pointIndex)
|
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);
|
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)
|
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start);
|
osg::Vec3f vectorStart = Misc::Convert::makeOsgVec3f(start);
|
||||||
osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart;
|
osg::Vec3f delta = Misc::Convert::makeOsgVec3f(end) - vectorStart;
|
||||||
float length = delta.length();
|
float length = delta.length();
|
||||||
delta.normalize();
|
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
|
// 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;
|
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;
|
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]));
|
const osg::Vec3f position = storage.mAllowedPositions[i];
|
||||||
float tempDist = (mInitialActorPosition - nodePos).length2();
|
const float tempDist = (mInitialActorPosition - position).length2();
|
||||||
if (tempDist < distanceToClosestNode)
|
if (tempDist < distanceToClosestPosition)
|
||||||
{
|
{
|
||||||
index = i;
|
index = i;
|
||||||
distanceToClosestNode = tempDist;
|
distanceToClosestPosition = tempDist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
storage.mCurrentNode = storage.mAllowedNodes[index];
|
storage.mCurrentPosition = storage.mAllowedPositions[index];
|
||||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + index);
|
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::writeState(ESM::AiSequence::AiSequence& sequence) const
|
void AiWander::writeState(ESM::AiSequence::AiSequence& sequence) const
|
||||||
|
|
@ -970,8 +984,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
AiWander::AiWander(const ESM::AiSequence::AiWander* wander)
|
AiWander::AiWander(const ESM::AiSequence::AiWander* wander)
|
||||||
: TypedAiPackage<AiWander>(makeDefaultOptions().withRepeat(wander->mData.mShouldRepeat != 0))
|
: TypedAiPackage<AiWander>(makeDefaultOptions().withRepeat(wander->mData.mShouldRepeat != 0))
|
||||||
, mDistance(std::max(static_cast<short>(0), wander->mData.mDistance))
|
, mDistance(static_cast<unsigned>(std::max(static_cast<short>(0), wander->mData.mDistance)))
|
||||||
, mDuration(std::max(static_cast<short>(0), wander->mData.mDuration))
|
, mDuration(static_cast<unsigned>(std::max(static_cast<short>(0), wander->mData.mDuration)))
|
||||||
, mRemainingDuration(wander->mDurationData.mRemainingDuration)
|
, mRemainingDuration(wander->mDurationData.mRemainingDuration)
|
||||||
, mTimeOfDay(wander->mData.mTimeOfDay)
|
, mTimeOfDay(wander->mData.mTimeOfDay)
|
||||||
, mIdle(getInitialIdle(wander->mData.mIdle))
|
, mIdle(getInitialIdle(wander->mData.mIdle))
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
#ifndef GAME_MWMECHANICS_AIWANDER_H
|
#ifndef GAME_MWMECHANICS_AIWANDER_H
|
||||||
#define GAME_MWMECHANICS_AIWANDER_H
|
#define GAME_MWMECHANICS_AIWANDER_H
|
||||||
|
|
||||||
#include "typedaipackage.hpp"
|
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "aitemporarybase.hpp"
|
#include "aitemporarybase.hpp"
|
||||||
#include "aitimer.hpp"
|
#include "aitimer.hpp"
|
||||||
#include "pathfinding.hpp"
|
#include "pathfinding.hpp"
|
||||||
|
#include "typedaipackage.hpp"
|
||||||
|
|
||||||
|
#include <components/esm3/loadpgrd.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
|
@ -51,14 +52,13 @@ namespace MWMechanics
|
||||||
unsigned short mIdleAnimation;
|
unsigned short mIdleAnimation;
|
||||||
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
|
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
|
||||||
|
|
||||||
// do we need to calculate allowed nodes based on mDistance
|
bool mPopulateAvailablePositions;
|
||||||
bool mPopulateAvailableNodes;
|
|
||||||
|
|
||||||
// allowed pathgrid nodes based on mDistance from the spawn point
|
// allowed destination positions based on mDistance from the spawn point
|
||||||
std::vector<ESM::Pathgrid::Point> mAllowedNodes;
|
std::vector<osg::Vec3f> mAllowedPositions;
|
||||||
|
|
||||||
ESM::Pathgrid::Point mCurrentNode;
|
osg::Vec3f mCurrentPosition;
|
||||||
bool mTrimCurrentNode;
|
bool mTrimCurrentPosition;
|
||||||
|
|
||||||
float mCheckIdlePositionTimer;
|
float mCheckIdlePositionTimer;
|
||||||
int mStuckCount;
|
int mStuckCount;
|
||||||
|
|
@ -132,7 +132,8 @@ namespace MWMechanics
|
||||||
bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||||
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||||
int getRandomIdle() const;
|
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 evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration,
|
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration,
|
||||||
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage);
|
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage);
|
||||||
|
|
@ -145,28 +146,27 @@ namespace MWMechanics
|
||||||
void wanderNearStart(const MWWorld::Ptr& actor, AiWanderStorage& storage, int wanderDistance);
|
void wanderNearStart(const MWWorld::Ptr& actor, AiWanderStorage& storage, int wanderDistance);
|
||||||
bool destinationIsAtWater(const MWWorld::Ptr& actor, const osg::Vec3f& destination);
|
bool destinationIsAtWater(const MWWorld::Ptr& actor, const osg::Vec3f& destination);
|
||||||
void completeManualWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
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
|
// how far the actor can wander from the spawn point
|
||||||
const int mDuration;
|
const unsigned mDistance;
|
||||||
|
const unsigned mDuration;
|
||||||
float mRemainingDuration;
|
float mRemainingDuration;
|
||||||
const int mTimeOfDay;
|
const int mTimeOfDay;
|
||||||
const std::vector<unsigned char> mIdle;
|
const std::vector<unsigned char> mIdle;
|
||||||
|
|
||||||
bool mStoredInitialActorPosition;
|
bool mStoredInitialActorPosition;
|
||||||
osg::Vec3f
|
// Note: an original engine does not reset coordinates even when actor changes a cell
|
||||||
mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell
|
osg::Vec3f mInitialActorPosition;
|
||||||
|
|
||||||
bool mHasDestination;
|
bool mHasDestination;
|
||||||
osg::Vec3f mDestination;
|
osg::Vec3f mDestination;
|
||||||
bool mUsePathgrid;
|
bool mUsePathgrid;
|
||||||
|
|
||||||
void getNeighbouringNodes(
|
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 fillAllowedPositions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
|
|
||||||
void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder);
|
|
||||||
|
|
||||||
// constants for converting idleSelect values into groupNames
|
// constants for converting idleSelect values into groupNames
|
||||||
enum GroupIndex
|
enum GroupIndex
|
||||||
|
|
@ -175,12 +175,12 @@ namespace MWMechanics
|
||||||
GroupIndex_MaxIdle = 9
|
GroupIndex_MaxIdle = 9
|
||||||
};
|
};
|
||||||
|
|
||||||
void setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage);
|
void setCurrentPositionToClosestAllowedPosition(AiWanderStorage& storage);
|
||||||
|
|
||||||
void addNonPathGridAllowedPoints(const ESM::Pathgrid* pathGrid, size_t pointIndex, AiWanderStorage& storage,
|
void addNonPathGridAllowedPoints(const ESM::Pathgrid* pathGrid, size_t pointIndex, AiWanderStorage& storage,
|
||||||
const Misc::CoordinateConverter& converter);
|
const Misc::CoordinateConverter& converter);
|
||||||
|
|
||||||
void AddPointBetweenPathGridPoints(
|
void addPositionBetweenPathgridPoints(
|
||||||
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage);
|
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage);
|
||||||
|
|
||||||
/// lookup table for converting idleSelect value to groupName
|
/// lookup table for converting idleSelect value to groupName
|
||||||
|
|
|
||||||
|
|
@ -2310,17 +2310,13 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
else
|
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.
|
// It seems only bipedal actors use turning animations.
|
||||||
// Also do not use turning animations in the first-person view and when sneaking.
|
// Also do not use turning animations in the first-person view and when sneaking.
|
||||||
if (!sneak && !isFirstPersonPlayer && isBiped)
|
if (!sneak && !isFirstPersonPlayer && isBiped)
|
||||||
{
|
{
|
||||||
if (effectiveRotation > rotationThreshold)
|
if (effectiveRotation > 0.f)
|
||||||
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
|
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
|
||||||
else if (effectiveRotation < -rotationThreshold)
|
else if (effectiveRotation < 0.f)
|
||||||
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
|
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2346,34 +2342,19 @@ namespace MWMechanics
|
||||||
vec.y() *= std::sqrt(1.0f - swimUpwardCoef * swimUpwardCoef);
|
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 (isBiped)
|
||||||
if (isPlayer)
|
|
||||||
{
|
{
|
||||||
float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f;
|
if (mTurnAnimationThreshold > 0)
|
||||||
float complete;
|
mTurnAnimationThreshold -= duration;
|
||||||
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 (movestate == CharState_TurnRight || movestate == CharState_TurnLeft
|
if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft
|
||||||
|| movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft)
|
|| movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft)
|
||||||
{
|
{
|
||||||
mTurnAnimationThreshold = 0.05f;
|
mTurnAnimationThreshold = 0.05f;
|
||||||
}
|
}
|
||||||
else if (movestate == CharState_None && isTurning() && mTurnAnimationThreshold > 0)
|
else if (movestate == CharState_None && isTurning() && mTurnAnimationThreshold > 0)
|
||||||
{
|
{
|
||||||
movestate = mMovementState;
|
movestate = mMovementState;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2402,11 +2383,10 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (isTurning())
|
if (isTurning())
|
||||||
{
|
{
|
||||||
// Adjust animation speed from 1.0 to 1.5 multiplier
|
|
||||||
if (duration > 0)
|
if (duration > 0)
|
||||||
{
|
{
|
||||||
float turnSpeed = std::min(1.5f, std::abs(rot.z()) / duration / static_cast<float>(osg::PI));
|
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)
|
else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed)
|
||||||
|
|
|
||||||
|
|
@ -106,21 +106,6 @@ namespace MWMechanics
|
||||||
return visitor.mResult;
|
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()
|
ObstacleCheck::ObstacleCheck()
|
||||||
: mEvadeDirectionIndex(std::size(evadeDirections) - 1)
|
: mEvadeDirectionIndex(std::size(evadeDirections) - 1)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
#include <osg/Vec3f>
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class Ptr;
|
class Ptr;
|
||||||
|
|
@ -24,9 +22,6 @@ namespace MWMechanics
|
||||||
/** \return Pointer to the door, or empty pointer if none exists **/
|
/** \return Pointer to the door, or empty pointer if none exists **/
|
||||||
const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist);
|
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
|
class ObstacleCheck
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <components/detournavigator/navigatorutils.hpp>
|
#include <components/detournavigator/navigatorutils.hpp>
|
||||||
#include <components/misc/coordinateconverter.hpp>
|
#include <components/misc/coordinateconverter.hpp>
|
||||||
#include <components/misc/math.hpp>
|
#include <components/misc/math.hpp>
|
||||||
|
#include <components/misc/pathgridutils.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
@ -38,7 +39,7 @@ namespace
|
||||||
// points to a quadtree may help
|
// points to a quadtree may help
|
||||||
for (size_t counter = 0; counter < grid->mPoints.size(); counter++)
|
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)
|
if (potentialDistBetween < closestDistanceReachable)
|
||||||
{
|
{
|
||||||
// found a closer one
|
// found a closer one
|
||||||
|
|
@ -197,7 +198,7 @@ namespace MWMechanics
|
||||||
// point right behind the wall that is closer than any pathgrid
|
// point right behind the wall that is closer than any pathgrid
|
||||||
// point outside the wall
|
// point outside the wall
|
||||||
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
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));
|
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
||||||
std::pair<size_t, bool> endNode
|
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
|
// 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.
|
// start or end to nearest pathgrid point, just travel from start to end.
|
||||||
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
||||||
float endTolastNodeLength2 = distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
float endTolastNodeLength2 = Misc::distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||||
float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
float startTo1stNodeLength2 = Misc::distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||||
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
||||||
{
|
{
|
||||||
*out++ = endPoint;
|
*out++ = endPoint;
|
||||||
|
|
@ -223,7 +224,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]);
|
ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]);
|
||||||
converter.toWorld(temp);
|
converter.toWorld(temp);
|
||||||
*out++ = makeOsgVec3(temp);
|
*out++ = Misc::Convert::makeOsgVec3f(temp);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -234,8 +235,8 @@ namespace MWMechanics
|
||||||
if (path.size() > 1)
|
if (path.size() > 1)
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::Point secondNode = *(++path.begin());
|
ESM::Pathgrid::Point secondNode = *(++path.begin());
|
||||||
osg::Vec3f firstNodeVec3f = makeOsgVec3(pathgrid->mPoints[startNode]);
|
osg::Vec3f firstNodeVec3f = Misc::Convert::makeOsgVec3f(pathgrid->mPoints[startNode]);
|
||||||
osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode);
|
osg::Vec3f secondNodeVec3f = Misc::Convert::makeOsgVec3f(secondNode);
|
||||||
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
||||||
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
||||||
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
||||||
|
|
@ -259,7 +260,7 @@ namespace MWMechanics
|
||||||
// convert supplied path to world coordinates
|
// convert supplied path to world coordinates
|
||||||
std::transform(path.begin(), path.end(), out, [&](ESM::Pathgrid::Point& point) {
|
std::transform(path.begin(), path.end(), out, [&](ESM::Pathgrid::Point& point) {
|
||||||
converter.toWorld(point);
|
converter.toWorld(point);
|
||||||
return makeOsgVec3(point);
|
return Misc::Convert::makeOsgVec3f(point);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -359,26 +360,16 @@ namespace MWMechanics
|
||||||
mConstructed = true;
|
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,
|
void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||||
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
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();
|
mPath.clear();
|
||||||
|
|
||||||
// If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path
|
// 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,
|
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)
|
if (status != DetourNavigator::Status::Success)
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
|
|
@ -390,19 +381,19 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
PathType pathType, std::span<const osg::Vec3f> checkpoints)
|
||||||
{
|
{
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
mCell = cell;
|
mCell = actor.getCell();
|
||||||
|
|
||||||
DetourNavigator::Status status = DetourNavigator::Status::NavMeshNotFound;
|
DetourNavigator::Status status = DetourNavigator::Status::NavMeshNotFound;
|
||||||
|
|
||||||
if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor))
|
if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor))
|
||||||
{
|
{
|
||||||
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags, areaCosts, endTolerance,
|
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)
|
if (status != DetourNavigator::Status::Success)
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
}
|
}
|
||||||
|
|
@ -411,7 +402,7 @@ namespace MWMechanics
|
||||||
&& (flags & DetourNavigator::Flag_usePathgrid) == 0)
|
&& (flags & DetourNavigator::Flag_usePathgrid) == 0)
|
||||||
{
|
{
|
||||||
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds,
|
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));
|
std::back_inserter(mPath));
|
||||||
if (status != DetourNavigator::Status::Success)
|
if (status != DetourNavigator::Status::Success)
|
||||||
mPath.clear();
|
mPath.clear();
|
||||||
|
|
@ -429,12 +420,13 @@ namespace MWMechanics
|
||||||
DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
||||||
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
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 MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||||
const auto navigator = world->getNavigator();
|
const DetourNavigator::Navigator& navigator = *world.getNavigator();
|
||||||
const auto status = DetourNavigator::findPath(
|
const DetourNavigator::Status status = DetourNavigator::findPath(
|
||||||
*navigator, agentBounds, startPoint, endPoint, flags, areaCosts, endTolerance, out);
|
navigator, agentBounds, startPoint, endPoint, flags, areaCosts, endTolerance, checkpoints, out);
|
||||||
|
|
||||||
if (pathType == PathType::Partial && status == DetourNavigator::Status::PartialPath)
|
if (pathType == PathType::Partial && status == DetourNavigator::Status::PartialPath)
|
||||||
return DetourNavigator::Status::Success;
|
return DetourNavigator::Status::Success;
|
||||||
|
|
@ -451,9 +443,9 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||||
const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
const osg::Vec3f& endPoint, const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
PathType pathType)
|
||||||
{
|
{
|
||||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||||
const auto maxDistance
|
const auto maxDistance
|
||||||
|
|
@ -461,9 +453,9 @@ namespace MWMechanics
|
||||||
const auto startToEnd = endPoint - startPoint;
|
const auto startToEnd = endPoint - startPoint;
|
||||||
const auto distance = startToEnd.length();
|
const auto distance = startToEnd.length();
|
||||||
if (distance <= maxDistance)
|
if (distance <= maxDistance)
|
||||||
return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, agentBounds, flags, areaCosts,
|
return buildPath(
|
||||||
endTolerance, pathType);
|
actor, startPoint, endPoint, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType);
|
||||||
const auto end = startPoint + startToEnd * maxDistance / distance;
|
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 <cassert>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
#include <components/detournavigator/areatype.hpp>
|
#include <components/detournavigator/areatype.hpp>
|
||||||
#include <components/detournavigator/flags.hpp>
|
#include <components/detournavigator/flags.hpp>
|
||||||
#include <components/detournavigator/status.hpp>
|
#include <components/detournavigator/status.hpp>
|
||||||
#include <components/esm/position.hpp>
|
|
||||||
#include <components/esm3/loadpgrd.hpp>
|
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
|
|
@ -102,23 +103,20 @@ namespace MWMechanics
|
||||||
|
|
||||||
void buildStraightPath(const osg::Vec3f& endPoint);
|
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,
|
void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||||
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
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,
|
void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType);
|
PathType pathType, std::span<const osg::Vec3f> checkpoints = {});
|
||||||
|
|
||||||
void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType);
|
PathType pathType);
|
||||||
|
|
||||||
/// Remove front point if exist and within tolerance
|
/// Remove front point if exist and within tolerance
|
||||||
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
||||||
|
|
@ -145,61 +143,6 @@ namespace MWMechanics
|
||||||
mPath.push_back(point);
|
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:
|
private:
|
||||||
bool mConstructed = false;
|
bool mConstructed = false;
|
||||||
std::deque<osg::Vec3f> mPath;
|
std::deque<osg::Vec3f> mPath;
|
||||||
|
|
@ -211,7 +154,8 @@ namespace MWMechanics
|
||||||
[[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
[[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
||||||
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
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);
|
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||||
effect.mDuration = hasDuration ? static_cast<float>(enam.mData.mDuration) : 1.f;
|
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;
|
effect.mTimeLeft = effect.mDuration;
|
||||||
|
|
||||||
// add to list of active effects, to apply in next frame
|
// add to list of active effects, to apply in next frame
|
||||||
|
|
|
||||||
|
|
@ -377,8 +377,11 @@ namespace
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||||
return MWMechanics::MagicApplicationResult::Type::REMOVED;
|
return MWMechanics::MagicApplicationResult::Type::REMOVED;
|
||||||
}
|
}
|
||||||
effect.mMinMagnitude *= magnitudeMult;
|
else if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||||
effect.mMaxMagnitude *= magnitudeMult;
|
{
|
||||||
|
effect.mMinMagnitude *= magnitudeMult;
|
||||||
|
effect.mMaxMagnitude *= magnitudeMult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return MWMechanics::MagicApplicationResult::Type::APPLIED;
|
return MWMechanics::MagicApplicationResult::Type::APPLIED;
|
||||||
}
|
}
|
||||||
|
|
@ -1011,11 +1014,13 @@ namespace MWMechanics
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Morrowind.exe doesn't apply magic effects while the menu is open, we do because we like to see stats
|
// 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
|
if (!dt
|
||||||
&& (effect.mEffectId == ESM::MagicEffect::Recall
|
&& (effect.mEffectId == ESM::MagicEffect::Recall
|
||||||
|| effect.mEffectId == ESM::MagicEffect::DivineIntervention
|
|| 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 };
|
return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth };
|
||||||
auto& stats = target.getClass().getCreatureStats(target);
|
auto& stats = target.getClass().getCreatureStats(target);
|
||||||
auto& magnitudes = stats.getMagicEffects();
|
auto& magnitudes = stats.getMagicEffects();
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
// https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection
|
// 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& aabbMin, const btVector3& aabbMax, const btVector3& position, const btScalar radius)
|
||||||
{
|
{
|
||||||
const btVector3 nearest(std::clamp(position.x(), aabbMin.x(), aabbMax.x()),
|
const btVector3 nearest(std::clamp(position.x(), aabbMin.x(), aabbMax.x()),
|
||||||
|
|
@ -18,35 +18,28 @@ namespace MWPhysics
|
||||||
return nearest.distance(position) < radius;
|
return nearest.distance(position) < radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Ignore, class OnCollision>
|
|
||||||
class HasSphereCollisionCallback final : public btBroadphaseAabbCallback
|
class HasSphereCollisionCallback final : public btBroadphaseAabbCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask, const int group,
|
explicit HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask,
|
||||||
const Ignore& ignore, OnCollision* onCollision)
|
const int group, const btCollisionObject* ignore)
|
||||||
: mPosition(position)
|
: mPosition(position)
|
||||||
, mRadius(radius)
|
, mRadius(radius)
|
||||||
, mIgnore(ignore)
|
, mIgnore(ignore)
|
||||||
, mCollisionFilterMask(mask)
|
, mCollisionFilterMask(mask)
|
||||||
, mCollisionFilterGroup(group)
|
, mCollisionFilterGroup(group)
|
||||||
, mOnCollision(onCollision)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool process(const btBroadphaseProxy* proxy) override
|
bool process(const btBroadphaseProxy* proxy) override
|
||||||
{
|
{
|
||||||
if (mResult && mOnCollision == nullptr)
|
if (mResult)
|
||||||
return false;
|
return false;
|
||||||
const auto collisionObject = static_cast<btCollisionObject*>(proxy->m_clientObject);
|
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))
|
|| !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius))
|
||||||
return true;
|
return true;
|
||||||
mResult = true;
|
mResult = true;
|
||||||
if (mOnCollision != nullptr)
|
|
||||||
{
|
|
||||||
(*mOnCollision)(collisionObject);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !mResult;
|
return !mResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,10 +48,9 @@ namespace MWPhysics
|
||||||
private:
|
private:
|
||||||
btVector3 mPosition;
|
btVector3 mPosition;
|
||||||
btScalar mRadius;
|
btScalar mRadius;
|
||||||
Ignore mIgnore;
|
const btCollisionObject* mIgnore;
|
||||||
int mCollisionFilterMask;
|
int mCollisionFilterMask;
|
||||||
int mCollisionFilterGroup;
|
int mCollisionFilterGroup;
|
||||||
OnCollision* mOnCollision;
|
|
||||||
bool mResult = false;
|
bool mResult = false;
|
||||||
|
|
||||||
bool needsCollision(const btBroadphaseProxy& proxy) const
|
bool needsCollision(const btBroadphaseProxy& proxy) const
|
||||||
|
|
|
||||||
|
|
@ -849,36 +849,18 @@ namespace MWPhysics
|
||||||
mWaterCollisionObject.get(), CollisionType_Water, CollisionType_Actor | CollisionType_Projectile);
|
mWaterCollisionObject.get(), CollisionType_Water, CollisionType_Actor | CollisionType_Projectile);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
bool PhysicsSystem::isAreaOccupiedByOtherActor(
|
||||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const
|
const MWWorld::LiveCellRefBase* actor, const osg::Vec3f& position, const float radius) const
|
||||||
{
|
{
|
||||||
std::vector<const btCollisionObject*> ignoredObjects;
|
const btCollisionObject* ignoredObject = nullptr;
|
||||||
ignoredObjects.reserve(ignore.size());
|
if (const auto it = mActors.find(actor); it != mActors.end())
|
||||||
for (const auto& v : ignore)
|
ignoredObject = it->second->getCollisionObject();
|
||||||
if (const auto it = mActors.find(v.mRef); it != mActors.end())
|
const btVector3 bulletPosition = Misc::Convert::toBullet(position);
|
||||||
ignoredObjects.push_back(it->second->getCollisionObject());
|
const btVector3 aabbMin = bulletPosition - btVector3(radius, radius, radius);
|
||||||
std::sort(ignoredObjects.begin(), ignoredObjects.end());
|
const btVector3 aabbMax = bulletPosition + btVector3(radius, radius, radius);
|
||||||
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 int mask = MWPhysics::CollisionType_Actor;
|
const int mask = MWPhysics::CollisionType_Actor;
|
||||||
const int group = MWPhysics::CollisionType_AnyPhysical;
|
const int group = MWPhysics::CollisionType_AnyPhysical;
|
||||||
if (occupyingActors == nullptr)
|
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoredObject);
|
||||||
{
|
|
||||||
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);
|
|
||||||
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
|
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
|
||||||
return callback.getResult();
|
return callback.getResult();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -281,8 +281,8 @@ namespace MWPhysics
|
||||||
std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function);
|
std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
bool isAreaOccupiedByOtherActor(
|
||||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const;
|
const MWWorld::LiveCellRefBase* actor, const osg::Vec3f& position, float radius) const;
|
||||||
|
|
||||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||||
void reportCollision(const btVector3& position, const btVector3& normal);
|
void reportCollision(const btVector3& position, const btVector3& normal);
|
||||||
|
|
|
||||||
|
|
@ -566,10 +566,10 @@ namespace MWWorld
|
||||||
std::vector<Ref> refs;
|
std::vector<Ref> refs;
|
||||||
std::set<ESM::RefId> keyIDs;
|
std::set<ESM::RefId> keyIDs;
|
||||||
std::vector<ESM::RefId> refIDs;
|
std::vector<ESM::RefId> refIDs;
|
||||||
Store<ESM::Cell> Cells = get<ESM::Cell>();
|
const Store<ESM::Cell>& cells = get<ESM::Cell>();
|
||||||
for (auto it = Cells.intBegin(); it != Cells.intEnd(); ++it)
|
for (auto it = cells.intBegin(); it != cells.intEnd(); ++it)
|
||||||
readRefs(*it, refs, refIDs, keyIDs, readers);
|
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);
|
readRefs(*it, refs, refIDs, keyIDs, readers);
|
||||||
const auto lessByRefNum = [](const Ref& l, const Ref& r) { return l.mRefNum < r.mRefNum; };
|
const auto lessByRefNum = [](const Ref& l, const Ref& r) { return l.mRefNum < r.mRefNum; };
|
||||||
std::stable_sort(refs.begin(), refs.end(), lessByRefNum);
|
std::stable_sort(refs.begin(), refs.end(), lessByRefNum);
|
||||||
|
|
|
||||||
|
|
@ -3871,10 +3871,11 @@ namespace MWWorld
|
||||||
return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal);
|
return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
bool World::isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const
|
||||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) 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
|
void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
||||||
|
|
|
||||||
|
|
@ -664,8 +664,7 @@ namespace MWWorld
|
||||||
bool hasCollisionWithDoor(
|
bool hasCollisionWithDoor(
|
||||||
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override;
|
const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override;
|
||||||
|
|
||||||
bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const override;
|
||||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const override;
|
|
||||||
|
|
||||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
|
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include <components/esm3/loadnpc.hpp>
|
#include <components/esm3/loadnpc.hpp>
|
||||||
#include <components/esm3/readerscache.hpp>
|
#include <components/esm3/readerscache.hpp>
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
|
|
@ -36,7 +37,7 @@ namespace MWWorld
|
||||||
LiveCellRef<ESM::NPC> liveCellRef(cellRef, &npc);
|
LiveCellRef<ESM::NPC> liveCellRef(cellRef, &npc);
|
||||||
liveCellRef.mData.setDeletedByContentFile(true);
|
liveCellRef.mData.setDeletedByContentFile(true);
|
||||||
Ptr ptr(&liveCellRef);
|
Ptr ptr(&liveCellRef);
|
||||||
EXPECT_EQ(ptr.toString(), "deleted object0xd00002a (NPC, \"player\")");
|
EXPECT_THAT(ptr.toString(), StrCaseEq("deleted object0xd00002a (NPC, \"player\")"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MWWorldPtrTest, toStringShouldReturnHumanReadableTextRepresentationOfPtr)
|
TEST(MWWorldPtrTest, toStringShouldReturnHumanReadableTextRepresentationOfPtr)
|
||||||
|
|
@ -53,7 +54,7 @@ namespace MWWorld
|
||||||
cellRef.mRefNum = ESM::RefNum{ .mIndex = 0x2a, .mContentFile = 0xd };
|
cellRef.mRefNum = ESM::RefNum{ .mIndex = 0x2a, .mContentFile = 0xd };
|
||||||
LiveCellRef<ESM::NPC> liveCellRef(cellRef, &npc);
|
LiveCellRef<ESM::NPC> liveCellRef(cellRef, &npc);
|
||||||
Ptr ptr(&liveCellRef);
|
Ptr ptr(&liveCellRef);
|
||||||
EXPECT_EQ(ptr.toString(), "object0xd00002a (NPC, \"player\")");
|
EXPECT_THAT(ptr.toString(), StrCaseEq("object0xd00002a (NPC, \"player\")"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MWWorldPtrTest, underlyingLiveCellRefShouldBeDeregisteredOnDestruction)
|
TEST(MWWorldPtrTest, underlyingLiveCellRefShouldBeDeregisteredOnDestruction)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
|
|
||||||
#include <osg/Vec3f>
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
@ -64,6 +63,25 @@ namespace DetourNavigator
|
||||||
std::reference_wrapper<const RecastSettings> mSettings;
|
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,
|
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,
|
const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter,
|
||||||
std::span<dtPolyRef> pathBuffer)
|
std::span<dtPolyRef> pathBuffer)
|
||||||
|
|
@ -79,7 +97,7 @@ namespace DetourNavigator
|
||||||
}
|
}
|
||||||
|
|
||||||
Status makeSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& start, const osg::Vec3f& end,
|
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)
|
std::output_iterator<osg::Vec3f> auto& out)
|
||||||
{
|
{
|
||||||
assert(polygonPathSize <= polygonPath.size());
|
assert(polygonPathSize <= polygonPath.size());
|
||||||
|
|
@ -95,7 +113,7 @@ namespace DetourNavigator
|
||||||
dtStatusFailed(status))
|
dtStatusFailed(status))
|
||||||
return Status::FindStraightPathFailed;
|
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]);
|
*out++ = Misc::Convert::makeOsgVec3f(&cornerVertsBuffer[static_cast<std::size_t>(i) * 3]);
|
||||||
|
|
||||||
return Status::Success;
|
return Status::Success;
|
||||||
|
|
@ -103,7 +121,8 @@ namespace DetourNavigator
|
||||||
|
|
||||||
Status findSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& halfExtents, const osg::Vec3f& start,
|
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,
|
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;
|
dtQueryFilter queryFilter;
|
||||||
queryFilter.setIncludeFlags(includeFlags);
|
queryFilter.setIncludeFlags(includeFlags);
|
||||||
|
|
@ -131,29 +150,66 @@ namespace DetourNavigator
|
||||||
return Status::EndPolygonNotFound;
|
return Status::EndPolygonNotFound;
|
||||||
|
|
||||||
std::vector<dtPolyRef> polygonPath(settings.mMaxPolygonPathSize);
|
std::vector<dtPolyRef> polygonPath(settings.mMaxPolygonPathSize);
|
||||||
const auto polygonPathSize
|
std::span<dtPolyRef> polygonPathBuffer = polygonPath;
|
||||||
= findPolygonPath(navMeshQuery, startRef, endRef, startNavMeshPos, endNavMeshPos, queryFilter, 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;
|
return Status::FindPathOverPolygonsFailed;
|
||||||
|
|
||||||
if (*polygonPathSize == 0)
|
if (*toEndPathSize == 0)
|
||||||
return Status::Success;
|
return currentRef == endRef ? Status::Success : Status::PartialPath;
|
||||||
|
|
||||||
osg::Vec3f targetNavMeshPos;
|
osg::Vec3f targetNavMeshPos;
|
||||||
if (const dtStatus status = navMeshQuery.closestPointOnPoly(
|
if (const dtStatus status = navMeshQuery.closestPointOnPoly(
|
||||||
polygonPath[*polygonPathSize - 1], end.ptr(), targetNavMeshPos.ptr(), nullptr);
|
polygonPath[*toEndPathSize - 1], end.ptr(), targetNavMeshPos.ptr(), nullptr);
|
||||||
dtStatusFailed(status))
|
dtStatusFailed(status))
|
||||||
return Status::TargetPolygonNotFound;
|
return Status::TargetPolygonNotFound;
|
||||||
|
|
||||||
const bool partialPath = polygonPath[*polygonPathSize - 1] != endRef;
|
const Status smoothStatus = makeSmoothPath(navMeshQuery, currentNavMeshPos, targetNavMeshPos, polygonPath,
|
||||||
const Status smoothStatus = makeSmoothPath(navMeshQuery, startNavMeshPos, targetNavMeshPos, polygonPath,
|
*toEndPathSize, settings.mMaxSmoothPathSize, skipFirst, out);
|
||||||
*polygonPathSize, settings.mMaxSmoothPathSize, out);
|
|
||||||
|
|
||||||
if (smoothStatus != Status::Success)
|
if (smoothStatus != Status::Success)
|
||||||
return smoothStatus;
|
return smoothStatus;
|
||||||
|
|
||||||
return partialPath ? Status::PartialPath : Status::Success;
|
return polygonPath[*toEndPathSize - 1] == endRef ? Status::Success : Status::PartialPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
namespace DetourNavigator
|
namespace DetourNavigator
|
||||||
{
|
{
|
||||||
|
|
@ -21,13 +22,13 @@ namespace DetourNavigator
|
||||||
* @param end path at given point.
|
* @param end path at given point.
|
||||||
* @param includeFlags setup allowed navmesh areas.
|
* @param includeFlags setup allowed navmesh areas.
|
||||||
* @param out the beginning of the destination range.
|
* @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.
|
* @return Status.
|
||||||
* Equal to out if no path is found.
|
|
||||||
*/
|
*/
|
||||||
inline Status findPath(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start,
|
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,
|
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);
|
const auto navMesh = navigator.getNavMesh(agentBounds);
|
||||||
if (navMesh == nullptr)
|
if (navMesh == nullptr)
|
||||||
|
|
@ -37,7 +38,8 @@ namespace DetourNavigator
|
||||||
const auto locked = navMesh->lock();
|
const auto locked = navMesh->lock();
|
||||||
return findSmoothPath(locked->getQuery(), toNavMeshCoordinates(settings.mRecast, agentBounds.mHalfExtents),
|
return findSmoothPath(locked->getQuery(), toNavMeshCoordinates(settings.mRecast, agentBounds.mHalfExtents),
|
||||||
toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, end), includeFlags,
|
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(
|
inline std::pair<std::size_t, std::size_t> toCellAndLocal(
|
||||||
std::size_t begin, std::size_t global, std::size_t cellSize)
|
std::size_t begin, std::size_t global, std::size_t cellSize)
|
||||||
{
|
{
|
||||||
|
// NOLINTBEGIN(clang-analyzer-core.UndefinedBinaryOperatorResult)
|
||||||
std::size_t cell = global / (cellSize - 1);
|
std::size_t cell = global / (cellSize - 1);
|
||||||
|
// NOLINTEND(clang-analyzer-core.UndefinedBinaryOperatorResult)
|
||||||
std::size_t local = global & (cellSize - 2);
|
std::size_t local = global & (cellSize - 2);
|
||||||
if (global != begin && local == 0)
|
if (global != begin && local == 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,18 @@ namespace Misc
|
||||||
point.y() -= static_cast<float>(mCellY);
|
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
|
osg::Vec3f toLocalVec3(const osg::Vec3f& point) const
|
||||||
{
|
{
|
||||||
return osg::Vec3f(
|
osg::Vec3f result = point;
|
||||||
point.x() - static_cast<float>(mCellX), point.y() - static_cast<float>(mCellY), point.z());
|
toLocal(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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: "")
|
-- * `boneName` - name of the bone to attach the vfx to. (default: "")
|
||||||
-- * `particleTextureOverride` - name of the particle texture to use. (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: "").
|
-- * `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]
|
-- @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'})
|
-- 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] #AreaCosts areaCosts a table defining relative cost for each type of area.
|
||||||
-- @field [parent=#FindPathOptions] #number destinationTolerance a floating point number representing maximum allowed
|
-- @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).
|
-- 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}
|
-- 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).
|
-- * `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: "")
|
-- * `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)
|
-- * `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
|
-- @usage -- Spawn a sanctuary effect near the player
|
||||||
-- local effect = core.magic.effects.records[core.magic.EFFECT_TYPE.Sanctuary]
|
-- local effect = core.magic.effects.records[core.magic.EFFECT_TYPE.Sanctuary]
|
||||||
|
|
|
||||||
|
|
@ -315,6 +315,7 @@ registerPlayerTest('player rotation')
|
||||||
registerPlayerTest('player forward running')
|
registerPlayerTest('player forward running')
|
||||||
registerPlayerTest('player diagonal walking')
|
registerPlayerTest('player diagonal walking')
|
||||||
registerPlayerTest('findPath')
|
registerPlayerTest('findPath')
|
||||||
|
registerPlayerTest('findPath with checkpoints')
|
||||||
registerPlayerTest('findRandomPointAroundCircle')
|
registerPlayerTest('findRandomPointAroundCircle')
|
||||||
registerPlayerTest('castNavigationRay')
|
registerPlayerTest('castNavigationRay')
|
||||||
registerPlayerTest('findNearestNavMeshPosition')
|
registerPlayerTest('findNearestNavMeshPosition')
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ registerGlobalTest('player rotation', 'rotating player should not lead to nan ro
|
||||||
registerGlobalTest('player forward running')
|
registerGlobalTest('player forward running')
|
||||||
registerGlobalTest('player diagonal walking')
|
registerGlobalTest('player diagonal walking')
|
||||||
registerGlobalTest('findPath')
|
registerGlobalTest('findPath')
|
||||||
|
registerGlobalTest('findPath with checkpoints')
|
||||||
registerGlobalTest('findRandomPointAroundCircle')
|
registerGlobalTest('findRandomPointAroundCircle')
|
||||||
registerGlobalTest('castNavigationRay')
|
registerGlobalTest('castNavigationRay')
|
||||||
registerGlobalTest('findNearestNavMeshPosition')
|
registerGlobalTest('findNearestNavMeshPosition')
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,39 @@ testing.registerLocalTest('findPath',
|
||||||
testing.expectEqual(status, nearby.FIND_PATH_STATUS.Success, 'Status')
|
testing.expectEqual(status, nearby.FIND_PATH_STATUS.Success, 'Status')
|
||||||
testing.expectLessOrEqual((path[#path] - dst):length(), 1,
|
testing.expectLessOrEqual((path[#path] - dst):length(), 1,
|
||||||
'Last path point ' .. testing.formatActualExpected(path[#path], dst))
|
'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)
|
end)
|
||||||
|
|
||||||
testing.registerLocalTest('findRandomPointAroundCircle',
|
testing.registerLocalTest('findRandomPointAroundCircle',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue