mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-30 21:32:42 +00:00
1a12c453d6
Actors may have different collision shapes. Currently there are axis-aligned bounding boxes and rotating bounding boxes. With AABB it's required to use bounding cylinder for navmesh agent to avoid providing paths where actor can't pass. But for rotating bounding boxes cylinder with diameter equal to the front face width should be used to not reduce of available paths. For example rats have rotating bounding box as collision shape because of the difference between front and side faces width. * Add agent bounds to navmesh tile db cache key. This is required to distinguish tiles for agents with different bounds. * Increase navmesh version because navmesh tile db cache key and data has changed. * Move navmesh version to the code to avoid misconfiguration by users. * Fix all places where wrong half extents were used for pathfinding.
150 lines
5.5 KiB
C++
150 lines
5.5 KiB
C++
#include "aitravel.hpp"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <components/esm3/aisequence.hpp>
|
|
#include <components/detournavigator/agentbounds.hpp>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
|
|
#include "../mwworld/class.hpp"
|
|
#include "../mwworld/cellstore.hpp"
|
|
|
|
#include "movement.hpp"
|
|
#include "creaturestats.hpp"
|
|
|
|
namespace
|
|
{
|
|
|
|
constexpr float TRAVEL_FINISH_TIME = 2.f;
|
|
|
|
bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2)
|
|
{
|
|
// Maximum travel distance for vanilla compatibility.
|
|
// Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well.
|
|
// We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways.
|
|
return (pos1 - pos2).length2() <= 7168*7168;
|
|
}
|
|
|
|
}
|
|
|
|
namespace MWMechanics
|
|
{
|
|
AiTravel::AiTravel(float x, float y, float z, bool repeat, AiTravel*)
|
|
: TypedAiPackage<AiTravel>(repeat), mX(x), mY(y), mZ(z), mHidden(false), mDestinationTimer(TRAVEL_FINISH_TIME)
|
|
{
|
|
}
|
|
|
|
AiTravel::AiTravel(float x, float y, float z, AiInternalTravel* derived)
|
|
: TypedAiPackage<AiTravel>(derived), mX(x), mY(y), mZ(z), mHidden(true), mDestinationTimer(TRAVEL_FINISH_TIME)
|
|
{
|
|
}
|
|
|
|
AiTravel::AiTravel(float x, float y, float z, bool repeat)
|
|
: AiTravel(x, y, z, repeat, this)
|
|
{
|
|
}
|
|
|
|
AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel)
|
|
: TypedAiPackage<AiTravel>(travel->mRepeat), mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false)
|
|
, mDestinationTimer(TRAVEL_FINISH_TIME)
|
|
{
|
|
// Hidden ESM::AiSequence::AiTravel package should be converted into MWMechanics::AiInternalTravel type
|
|
assert(!travel->mHidden);
|
|
}
|
|
|
|
bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
|
|
{
|
|
MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager();
|
|
auto& stats = actor.getClass().getCreatureStats(actor);
|
|
|
|
if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) && !stats.getMovementFlag(CreatureStats::Flag_ForceSneak)
|
|
&& (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == Greet_InProgress))
|
|
return false;
|
|
|
|
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
|
const osg::Vec3f targetPos(mX, mY, mZ);
|
|
|
|
stats.setMovementFlag(CreatureStats::Flag_Run, false);
|
|
stats.setDrawState(DrawState_Nothing);
|
|
|
|
// Note: we should cancel internal "return after combat" package, if original location is too far away
|
|
if (!isWithinMaxRange(targetPos, actorPos))
|
|
return mHidden;
|
|
|
|
if (pathTo(actor, targetPos, duration))
|
|
{
|
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
|
return true;
|
|
}
|
|
|
|
// If we've been close enough to the destination for some time give up like Morrowind.
|
|
// The end condition should be pretty much accurate.
|
|
// FIXME: But the timing isn't. Right now we're being very generous,
|
|
// but Morrowind might stop the actor prematurely under unclear conditions.
|
|
|
|
// Note Morrowind uses the halved eye level, but this is close enough.
|
|
float dist = distanceIgnoreZ(actorPos, targetPos) - MWBase::Environment::get().getWorld()->getHalfExtents(actor).z();
|
|
const float endTolerance = std::max(64.f, actor.getClass().getCurrentSpeed(actor) * duration);
|
|
|
|
// Even if we have entered the threshold, we might have been pushed away. Reset the timer if we're currently too far.
|
|
if (dist > endTolerance)
|
|
{
|
|
mDestinationTimer = TRAVEL_FINISH_TIME;
|
|
return false;
|
|
}
|
|
|
|
mDestinationTimer -= duration;
|
|
if (mDestinationTimer > 0)
|
|
return false;
|
|
|
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
|
return true;
|
|
}
|
|
|
|
void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state)
|
|
{
|
|
osg::Vec3f pos(mX, mY, mZ);
|
|
if (!isWithinMaxRange(pos, actor.getRefData().getPosition().asVec3()))
|
|
return;
|
|
// does not do any validation on the travel target (whether it's in air, inside collision geometry, etc),
|
|
// that is the user's responsibility
|
|
MWBase::Environment::get().getWorld()->moveObject(actor, pos);
|
|
actor.getClass().adjustPosition(actor, false);
|
|
reset();
|
|
}
|
|
|
|
void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const
|
|
{
|
|
auto travel = std::make_unique<ESM::AiSequence::AiTravel>();
|
|
travel->mData.mX = mX;
|
|
travel->mData.mY = mY;
|
|
travel->mData.mZ = mZ;
|
|
travel->mHidden = mHidden;
|
|
travel->mRepeat = getRepeat();
|
|
|
|
ESM::AiSequence::AiPackageContainer package;
|
|
package.mType = ESM::AiSequence::Ai_Travel;
|
|
package.mPackage = std::move(travel);
|
|
sequence.mPackages.push_back(std::move(package));
|
|
}
|
|
|
|
AiInternalTravel::AiInternalTravel(float x, float y, float z)
|
|
: AiTravel(x, y, z, this)
|
|
{
|
|
}
|
|
|
|
AiInternalTravel::AiInternalTravel(const ESM::AiSequence::AiTravel* travel)
|
|
: AiTravel(travel->mData.mX, travel->mData.mY, travel->mData.mZ, this)
|
|
{
|
|
}
|
|
|
|
std::unique_ptr<AiPackage> AiInternalTravel::clone() const
|
|
{
|
|
return std::make_unique<AiInternalTravel>(*this);
|
|
}
|
|
}
|
|
|