1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-16 10:49:57 +00:00
openmw-tes3mp/apps/openmw/mwmechanics/obstacle.cpp

208 lines
6.8 KiB
C++
Raw Normal View History

#include "obstacle.hpp"
2017-07-31 15:28:02 +00:00
#include <components/sceneutil/positionattitudetransform.hpp>
2015-07-25 02:14:22 +00:00
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
#include "movement.hpp"
namespace MWMechanics
{
// NOTE: determined empirically but probably need further tweaking
static const float DIST_SAME_SPOT = 0.5f;
static const float DURATION_SAME_SPOT = 1.5f;
static const float DURATION_TO_EVADE = 0.4f;
const float ObstacleCheck::evadeDirections[NUM_EVADE_DIRECTIONS][2] =
{
{ 1.0f, 0.0f }, // move to side
{ 1.0f, -1.0f }, // move to side and backwards
{ -1.0f, 0.0f }, // move to other side
{ -1.0f, -1.0f } // move to side and backwards
};
bool proximityToDoor(const MWWorld::Ptr& actor, float minDist)
{
if(getNearbyDoor(actor, minDist).isEmpty())
return false;
else
return true;
}
const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist)
{
2017-07-31 15:28:02 +00:00
MWWorld::CellStore *cell = actor.getCell();
2017-07-31 15:28:02 +00:00
// Check all the doors in this cell
const MWWorld::CellRefList<ESM::Door>& doors = cell->getReadOnlyDoors();
const MWWorld::CellRefList<ESM::Door>::List& refList = doors.mList;
MWWorld::CellRefList<ESM::Door>::List::const_iterator it = refList.begin();
osg::Vec3f pos(actor.getRefData().getPosition().asVec3());
pos.z() = 0;
2017-07-31 15:28:02 +00:00
osg::Vec3f actorDir = (actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0));
2017-07-31 15:28:02 +00:00
for (; it != refList.end(); ++it)
{
const MWWorld::LiveCellRef<ESM::Door>& ref = *it;
osg::Vec3f doorPos(ref.mData.getPosition().asVec3());
// FIXME: cast
const MWWorld::Ptr doorPtr = MWWorld::Ptr(&const_cast<MWWorld::LiveCellRef<ESM::Door> &>(ref), actor.getCell());
int doorState = doorPtr.getClass().getDoorState(doorPtr);
float doorRot = ref.mData.getPosition().rot[2] - doorPtr.getCellRef().getPosition().rot[2];
if (doorState != 0 || doorRot != 0)
continue; // the door is already opened/opening
2017-07-31 15:28:02 +00:00
doorPos.z() = 0;
float angle = std::acos(actorDir * (doorPos - pos) / (actorDir.length() * (doorPos - pos).length()));
// Allow 60 degrees angle between actor and door
if (angle < -osg::PI / 3 || angle > osg::PI / 3)
continue;
2017-07-31 15:28:02 +00:00
// Door is not close enough
if ((pos - doorPos).length2() > minDist*minDist)
continue;
return doorPtr; // found, stop searching
2017-07-31 15:28:02 +00:00
}
2017-07-31 15:28:02 +00:00
return MWWorld::Ptr(); // none found
}
ObstacleCheck::ObstacleCheck():
mPrevX(0) // to see if the moved since last time
, mPrevY(0)
, mWalkState(State_Norm)
, mStuckDuration(0)
, mEvadeDuration(0)
, mDistSameSpot(-1) // avoid calculating it each time
, mEvadeDirectionIndex(0)
{
}
void ObstacleCheck::clear()
{
mWalkState = State_Norm;
mStuckDuration = 0;
mEvadeDuration = 0;
}
bool ObstacleCheck::isEvading() const
{
return mWalkState == State_Evade;
}
/*
* input - actor, duration (time since last check)
* output - true if evasive action needs to be taken
*
* Walking state transitions (player greeting check not shown):
*
* MoveNow <------------------------------------+
* | d|
* | |
* +-> State_Norm <---> State_CheckStuck --> State_Evade
* ^ ^ | f ^ | t ^ | |
* | | | | | | | |
* | +---+ +---+ +---+ | u
* | any < t < u |
* +--------------------------------------------+
*
* f = one reaction time
* d = proximity to a closed door
* t = how long before considered stuck
* u = how long to move sideways
*
*/
2019-03-19 22:59:09 +00:00
void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration)
{
const ESM::Position pos = actor.getRefData().getPosition();
if (mDistSameSpot == -1)
{
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor) + 1.2 * std::max(halfExtents.x(), halfExtents.y());
}
const float distSameSpot = mDistSameSpot * duration;
const float squaredMovedDistance = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2();
const bool samePosition = squaredMovedDistance < distSameSpot * distSameSpot;
// update position
mPrevX = pos.pos[0];
mPrevY = pos.pos[1];
switch(mWalkState)
{
case State_Norm:
{
if(!samePosition)
break;
else
mWalkState = State_CheckStuck;
}
/* FALL THROUGH */
case State_CheckStuck:
{
if(!samePosition)
{
mWalkState = State_Norm;
mStuckDuration = 0;
break;
}
else
{
mStuckDuration += duration;
// consider stuck only if position unchanges for a period
if(mStuckDuration < DURATION_SAME_SPOT)
break; // still checking, note duration added to timer
else
{
mStuckDuration = 0;
mWalkState = State_Evade;
chooseEvasionDirection();
}
}
}
/* FALL THROUGH */
case State_Evade:
{
mEvadeDuration += duration;
2018-08-18 15:26:00 +00:00
if(mEvadeDuration >= DURATION_TO_EVADE)
{
// tried to evade, assume all is ok and start again
mWalkState = State_Norm;
mEvadeDuration = 0;
}
}
/* NO DEFAULT CASE */
}
}
2018-08-18 20:17:52 +00:00
void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) const
{
actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex][0];
actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1];
}
void ObstacleCheck::chooseEvasionDirection()
{
// change direction if attempt didn't work
++mEvadeDirectionIndex;
if (mEvadeDirectionIndex == NUM_EVADE_DIRECTIONS)
{
mEvadeDirectionIndex = 0;
}
}
}