Merge branch 'more_evade_directions' into 'master'

Use more evade directions and filter by supporting animation (#7450)

Closes #7450

See merge request OpenMW/openmw!3210
revert-6246b479
psi29a 10 months ago
commit e073eab05e

@ -64,6 +64,7 @@
Bug #7413: Generated wilderness cells don't spawn fish
Bug #7415: Unbreakable lock discrepancies
Bug #7428: AutoCalc flag is not used to calculate enchantment costs
Bug #7450: Evading obstacles does not work for actors missing certain animations
Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously
Feature #3537: Shader-based water ripples
Feature #5492: Let rain and snow collide with statics

@ -9,6 +9,7 @@
#include "../mwworld/class.hpp"
#include "aicombataction.hpp"
#include "character.hpp"
#include "steering.hpp"
namespace MWMechanics
@ -48,7 +49,9 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
if (target.isEmpty())
return true;
if (!mManual && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, mDistance))
if (!mManual
&& !pathTo(actor, target.getRefData().getPosition().asVec3(), duration,
characterController.getSupportedMovementDirections(), mDistance))
{
return false;
}

@ -134,7 +134,8 @@ namespace MWMechanics
const osg::Vec3f destination = storage.mUseCustomDestination
? storage.mCustomDestination
: target.getRefData().getPosition().asVec3();
const bool is_target_reached = pathTo(actor, destination, duration, targetReachedTolerance);
const bool is_target_reached = pathTo(actor, destination, duration,
characterController.getSupportedMovementDirections(), targetReachedTolerance);
if (is_target_reached)
storage.mReadyToAttack = true;
}
@ -149,7 +150,7 @@ namespace MWMechanics
}
else
{
updateFleeing(actor, target, duration, storage);
updateFleeing(actor, target, duration, characterController.getSupportedMovementDirections(), storage);
}
storage.mActionCooldown -= duration;
@ -342,8 +343,8 @@ namespace MWMechanics
storage.mUpdateLOSTimer -= duration;
}
void MWMechanics::AiCombat::updateFleeing(
const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage)
void MWMechanics::AiCombat::updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirections, AiCombatStorage& storage)
{
static const float BLIND_RUN_DURATION = 1.0f;
@ -437,7 +438,7 @@ namespace MWMechanics
float dist
= (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
if ((dist > fFleeDistance && !storage.mLOS)
|| pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration))
|| pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration, supportedMovementDirections))
{
state = AiCombatStorage::FleeState_Idle;
}

@ -111,8 +111,8 @@ namespace MWMechanics
void updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage);
void updateFleeing(
const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage);
void updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirections, AiCombatStorage& storage);
/// Transfer desired movement (from AiCombatStorage) to Actor
void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage);

@ -11,6 +11,7 @@
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "character.hpp"
#include "creaturestats.hpp"
#include "movement.hpp"
@ -94,7 +95,7 @@ namespace MWMechanics
if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist)
{
const osg::Vec3f dest(mX, mY, mZ);
if (pathTo(actor, dest, duration, maxHalfExtent)) // Returns true on path complete
if (pathTo(actor, dest, duration, characterController.getSupportedMovementDirections(), maxHalfExtent))
{
mRemainingDuration = mDuration;
return true;

@ -11,6 +11,7 @@
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "character.hpp"
#include "creaturestats.hpp"
#include "steering.hpp"
@ -202,7 +203,9 @@ namespace MWMechanics
return false;
}
storage.mMoving = !pathTo(actor, targetPos, duration, baseFollowDistance); // Go to the destination
// Go to the destination
storage.mMoving = !pathTo(
actor, targetPos, duration, characterController.getSupportedMovementDirections(), baseFollowDistance);
if (storage.mMoving)
{

@ -111,7 +111,8 @@ void MWMechanics::AiPackage::reset()
}
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration,
float destTolerance, float endTolerance, PathType pathType)
MWWorld::MovementDirectionFlags supportedMovementDirections, float destTolerance, float endTolerance,
PathType pathType)
{
const Misc::TimerStatus timerStatus = mReaction.update(duration);
@ -231,7 +232,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
const auto destination = getNextPathPoint(dest);
mObstacleCheck.update(actor, destination, duration);
mObstacleCheck.update(actor, destination, duration, supportedMovementDirections);
if (smoothMovement)
{

@ -125,7 +125,8 @@ namespace MWMechanics
protected:
/// Handles path building and shortcutting with obstacles avoiding
/** \return If the actor has arrived at his destination **/
bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance = 0.0f,
bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirections, float destTolerance = 0.0f,
float endTolerance = 0.0f, PathType pathType = PathType::Full);
/// Check if there aren't any obstacles along the path to make shortcut possible

@ -10,6 +10,7 @@
#include "../mwworld/class.hpp"
#include "actorutil.hpp"
#include "character.hpp"
#include "creaturestats.hpp"
namespace MWMechanics
@ -55,7 +56,8 @@ namespace MWMechanics
const float pathTolerance = 100.f;
// check the true distance in case the target is far away in Z-direction
bool reached = pathTo(actor, dest, duration, pathTolerance, (actorPos - dest).length(), PathType::Partial)
bool reached = pathTo(actor, dest, duration, characterController.getSupportedMovementDirections(),
pathTolerance, (actorPos - dest).length(), PathType::Partial)
&& std::abs(dest.z() - actorPos.z()) < pathTolerance;
if (reached)

@ -10,6 +10,7 @@
#include "../mwworld/class.hpp"
#include "character.hpp"
#include "creaturestats.hpp"
#include "movement.hpp"
@ -89,7 +90,7 @@ namespace MWMechanics
if (!isWithinMaxRange(targetPos, actorPos))
return mHidden;
if (pathTo(actor, targetPos, duration))
if (pathTo(actor, targetPos, duration, characterController.getSupportedMovementDirections()))
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
return true;

@ -21,6 +21,7 @@
#include "../mwphysics/raycasting.hpp"
#include "actorutil.hpp"
#include "character.hpp"
#include "creaturestats.hpp"
#include "movement.hpp"
#include "pathgrid.hpp"
@ -190,7 +191,7 @@ namespace MWMechanics
* will kick in.
*/
bool AiWander::execute(
const MWWorld::Ptr& actor, CharacterController& /*characterController*/, AiState& state, float duration)
const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
{
MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor);
if (cStats.isDead() || cStats.getHealth().getCurrent() <= 0)
@ -244,7 +245,7 @@ namespace MWMechanics
}
}
doPerFrameActionsForState(actor, duration, storage);
doPerFrameActionsForState(actor, duration, characterController.getSupportedMovementDirections(), storage);
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
return false;
@ -454,7 +455,8 @@ namespace MWMechanics
storage.setState(AiWanderStorage::Wander_IdleNow);
}
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage)
{
switch (storage.mState)
{
@ -463,7 +465,7 @@ namespace MWMechanics
break;
case AiWanderStorage::Wander_Walking:
onWalkingStatePerFrameActions(actor, duration, storage);
onWalkingStatePerFrameActions(actor, duration, supportedMovementDirections, storage);
break;
case AiWanderStorage::Wander_ChooseAction:
@ -520,11 +522,13 @@ namespace MWMechanics
return false;
}
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage)
{
// Is there no destination or are we there yet?
if ((!mPathFinder.isPathConstructed())
|| pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE))
|| pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, supportedMovementDirections,
DESTINATION_TOLERANCE))
{
stopWalking(actor);
storage.setState(AiWanderStorage::Wander_ChooseAction);

@ -123,9 +123,11 @@ namespace MWMechanics
int getRandomIdle() const;
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage);
void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage);
void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos);
inline bool isPackageCompleted() const;

@ -19,6 +19,8 @@
#include "character.hpp"
#include <array>
#include <components/esm/records.hpp>
#include <components/misc/mathutil.hpp>
#include <components/misc/resourcehelpers.hpp>
@ -2926,4 +2928,77 @@ namespace MWMechanics
mAnimation->setHeadYaw(zAngleRadians);
}
MWWorld::MovementDirectionFlags CharacterController::getSupportedMovementDirections() const
{
using namespace std::string_view_literals;
// There are fallbacks in the CharacterController::refreshMovementAnims for certain animations. Arrays below
// represent them.
constexpr std::array all = { ""sv };
constexpr std::array walk = { "walk"sv };
constexpr std::array swimWalk = { "swimwalk"sv, "walk"sv };
constexpr std::array sneak = { "sneak"sv };
constexpr std::array run = { "run"sv, "walk"sv };
constexpr std::array swimRun = { "swimrun"sv, "run"sv, "walk"sv };
constexpr std::array swim = { "swim"sv };
switch (mMovementState)
{
case CharState_None:
case CharState_SpecialIdle:
case CharState_Idle:
case CharState_IdleSwim:
case CharState_IdleSneak:
return mAnimation->getSupportedMovementDirections(all);
case CharState_WalkForward:
case CharState_WalkBack:
case CharState_WalkLeft:
case CharState_WalkRight:
return mAnimation->getSupportedMovementDirections(walk);
case CharState_SwimWalkForward:
case CharState_SwimWalkBack:
case CharState_SwimWalkLeft:
case CharState_SwimWalkRight:
return mAnimation->getSupportedMovementDirections(swimWalk);
case CharState_RunForward:
case CharState_RunBack:
case CharState_RunLeft:
case CharState_RunRight:
return mAnimation->getSupportedMovementDirections(run);
case CharState_SwimRunForward:
case CharState_SwimRunBack:
case CharState_SwimRunLeft:
case CharState_SwimRunRight:
return mAnimation->getSupportedMovementDirections(swimRun);
case CharState_SneakForward:
case CharState_SneakBack:
case CharState_SneakLeft:
case CharState_SneakRight:
return mAnimation->getSupportedMovementDirections(sneak);
case CharState_TurnLeft:
case CharState_TurnRight:
return mAnimation->getSupportedMovementDirections(all);
case CharState_SwimTurnLeft:
case CharState_SwimTurnRight:
return mAnimation->getSupportedMovementDirections(swim);
case CharState_Death1:
case CharState_Death2:
case CharState_Death3:
case CharState_Death4:
case CharState_Death5:
case CharState_SwimDeath:
case CharState_SwimDeathKnockDown:
case CharState_SwimDeathKnockOut:
case CharState_DeathKnockDown:
case CharState_DeathKnockOut:
case CharState_Hit:
case CharState_SwimHit:
case CharState_KnockDown:
case CharState_KnockOut:
case CharState_SwimKnockDown:
case CharState_SwimKnockOut:
case CharState_Block:
return mAnimation->getSupportedMovementDirections(all);
}
return 0;
}
}

@ -315,6 +315,8 @@ namespace MWMechanics
void setHeadTrackTarget(const MWWorld::ConstPtr& target);
void playSwishSound() const;
MWWorld::MovementDirectionFlags getSupportedMovementDirections() const;
};
}

@ -1,6 +1,7 @@
#include "obstacle.hpp"
#include <array>
#include <span>
#include <components/detournavigator/agentbounds.hpp>
#include <components/esm3/loaddoor.hpp>
@ -15,17 +16,30 @@
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
};
namespace
{
// NOTE: determined empirically but probably need further tweaking
constexpr float distanceSameSpot = 0.5f;
constexpr float durationSameSpot = 1.5f;
constexpr float durationToEvade = 1;
struct EvadeDirection
{
float mMovementX;
float mMovementY;
MWWorld::MovementDirectionFlag mRequiredAnimation;
};
constexpr EvadeDirection evadeDirections[] = {
{ 1.0f, 1.0f, MWWorld::MovementDirectionFlag_Forward }, // move to right and forward
{ 1.0f, 0.0f, MWWorld::MovementDirectionFlag_Right }, // move to right
{ 1.0f, -1.0f, MWWorld::MovementDirectionFlag_Back }, // move to right and backwards
{ 0.0f, -1.0f, MWWorld::MovementDirectionFlag_Back }, // move backwards
{ -1.0f, -1.0f, MWWorld::MovementDirectionFlag_Back }, // move to left and backwards
{ -1.0f, 0.0f, MWWorld::MovementDirectionFlag_Left }, // move to left
{ -1.0f, 1.0f, MWWorld::MovementDirectionFlag_Forward }, // move to left and forward
};
}
bool proximityToDoor(const MWWorld::Ptr& actor, float minDist)
{
@ -94,9 +108,7 @@ namespace MWMechanics
}
ObstacleCheck::ObstacleCheck()
: mWalkState(WalkState::Initial)
, mStateDuration(0)
, mEvadeDirectionIndex(0)
: mEvadeDirectionIndex(std::size(evadeDirections) - 1)
{
}
@ -128,7 +140,8 @@ namespace MWMechanics
* u = how long to move sideways
*
*/
void ObstacleCheck::update(const MWWorld::Ptr& actor, const osg::Vec3f& destination, float duration)
void ObstacleCheck::update(const MWWorld::Ptr& actor, const osg::Vec3f& destination, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirection)
{
const auto position = actor.getRefData().getPosition().asVec3();
@ -150,7 +163,7 @@ namespace MWMechanics
mDestination = destination;
}
const float distSameSpot = DIST_SAME_SPOT * actor.getClass().getCurrentSpeed(actor) * duration;
const float distSameSpot = distanceSameSpot * actor.getClass().getCurrentSpeed(actor) * duration;
const float prevDistance = (destination - mPrev).length();
const float currentDistance = (destination - position).length();
const float movedDistance = prevDistance - currentDistance;
@ -174,19 +187,27 @@ namespace MWMechanics
}
mStateDuration += duration;
if (mStateDuration < DURATION_SAME_SPOT)
if (mStateDuration < durationSameSpot)
{
return;
}
mWalkState = WalkState::Evade;
mStateDuration = 0;
chooseEvasionDirection();
std::size_t newEvadeDirectionIndex = mEvadeDirectionIndex;
do
{
++newEvadeDirectionIndex;
if (newEvadeDirectionIndex == std::size(evadeDirections))
newEvadeDirectionIndex = 0;
if ((evadeDirections[newEvadeDirectionIndex].mRequiredAnimation & supportedMovementDirection) != 0)
break;
} while (mEvadeDirectionIndex != newEvadeDirectionIndex);
return;
}
mStateDuration += duration;
if (mStateDuration >= DURATION_TO_EVADE)
if (mStateDuration >= durationToEvade)
{
// tried to evade, assume all is ok and start again
mWalkState = WalkState::Norm;
@ -197,18 +218,7 @@ namespace MWMechanics
void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) const
{
actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex][0];
actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1];
actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex].mMovementX;
actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex].mMovementY;
}
void ObstacleCheck::chooseEvasionDirection()
{
// change direction if attempt didn't work
++mEvadeDirectionIndex;
if (mEvadeDirectionIndex == NUM_EVADE_DIRECTIONS)
{
mEvadeDirectionIndex = 0;
}
}
}

@ -1,6 +1,8 @@
#ifndef OPENMW_MECHANICS_OBSTACLE_H
#define OPENMW_MECHANICS_OBSTACLE_H
#include "apps/openmw/mwworld/movementdirection.hpp"
#include <osg/Vec3f>
#include <vector>
@ -15,8 +17,6 @@ namespace MWMechanics
{
struct Movement;
static constexpr int NUM_EVADE_DIRECTIONS = 4;
/// tests actor's proximity to a closed door by default
bool proximityToDoor(const MWWorld::Ptr& actor, float minDist);
@ -38,32 +38,27 @@ namespace MWMechanics
bool isEvading() const;
// Updates internal state, call each frame for moving actor
void update(const MWWorld::Ptr& actor, const osg::Vec3f& destination, float duration);
void update(const MWWorld::Ptr& actor, const osg::Vec3f& destination, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirection);
// change direction to try to fix "stuck" actor
void takeEvasiveAction(MWMechanics::Movement& actorMovement) const;
void takeEvasiveAction(Movement& actorMovement) const;
private:
osg::Vec3f mPrev;
osg::Vec3f mDestination;
// directions to try moving in when get stuck
static const float evadeDirections[NUM_EVADE_DIRECTIONS][2];
enum class WalkState
{
Initial,
Norm,
CheckStuck,
Evade
Evade,
};
WalkState mWalkState;
float mStateDuration;
int mEvadeDirectionIndex;
WalkState mWalkState = WalkState::Initial;
float mStateDuration = 0;
float mInitialDistance = 0;
void chooseEvasionDirection();
std::size_t mEvadeDirectionIndex;
osg::Vec3f mPrev;
osg::Vec3f mDestination;
};
}

@ -1,5 +1,6 @@
#include "animation.hpp"
#include <algorithm>
#include <iomanip>
#include <limits>
@ -660,6 +661,9 @@ namespace MWRender
mAnimSources.push_back(std::move(animsrc));
for (const std::string& group : mAnimSources.back()->getTextKeys().getGroups())
mSupportedAnimations.insert(group);
SceneUtil::AssignControllerSourcesVisitor assignVisitor(mAnimationTimePtr[0]);
mObjectRoot->accept(assignVisitor);
@ -698,6 +702,7 @@ namespace MWRender
mAccumCtrl = nullptr;
mSupportedAnimations.clear();
mAnimSources.clear();
mAnimVelocities.clear();
@ -705,15 +710,7 @@ namespace MWRender
bool Animation::hasAnimation(std::string_view anim) const
{
AnimSourceList::const_iterator iter(mAnimSources.begin());
for (; iter != mAnimSources.end(); ++iter)
{
const SceneUtil::TextKeyMap& keys = (*iter)->getTextKeys();
if (keys.hasGroupStart(anim))
return true;
}
return false;
return mSupportedAnimations.find(anim) != mSupportedAnimations.end();
}
float Animation::getStartTime(const std::string& groupname) const
@ -1804,6 +1801,28 @@ namespace MWRender
mInsert->removeChild(mObjectRoot);
}
MWWorld::MovementDirectionFlags Animation::getSupportedMovementDirections(
std::span<const std::string_view> prefixes) const
{
MWWorld::MovementDirectionFlags result = 0;
for (const std::string_view animation : mSupportedAnimations)
{
if (std::find_if(
prefixes.begin(), prefixes.end(), [&](std::string_view v) { return animation.starts_with(v); })
== prefixes.end())
continue;
if (animation.ends_with("forward"))
result |= MWWorld::MovementDirectionFlag_Forward;
else if (animation.ends_with("back"))
result |= MWWorld::MovementDirectionFlag_Back;
else if (animation.ends_with("left"))
result |= MWWorld::MovementDirectionFlag_Left;
else if (animation.ends_with("right"))
result |= MWWorld::MovementDirectionFlag_Right;
}
return result;
}
// ------------------------------------------------------
float Animation::AnimationTime::getValue(osg::NodeVisitor*)

@ -1,6 +1,7 @@
#ifndef GAME_RENDER_ANIMATION_H
#define GAME_RENDER_ANIMATION_H
#include "../mwworld/movementdirection.hpp"
#include "../mwworld/ptr.hpp"
#include <components/misc/strings/algorithm.hpp>
@ -9,7 +10,9 @@
#include <components/sceneutil/textkeymap.hpp>
#include <components/sceneutil/util.hpp>
#include <span>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace ESM
@ -231,6 +234,8 @@ namespace MWRender
typedef std::vector<std::shared_ptr<AnimSource>> AnimSourceList;
AnimSourceList mAnimSources;
std::unordered_set<std::string_view> mSupportedAnimations;
osg::ref_ptr<osg::Group> mInsert;
osg::ref_ptr<osg::Group> mObjectRoot;
@ -469,6 +474,9 @@ namespace MWRender
/// @note The matching is case-insensitive.
const osg::Node* getNode(std::string_view name) const;
MWWorld::MovementDirectionFlags getSupportedMovementDirections(
std::span<const std::string_view> prefixes) const;
virtual bool useShieldAnimations() const { return false; }
virtual bool getWeaponsShown() const { return false; }
virtual void showWeapons(bool showWeapon) {}

@ -0,0 +1,17 @@
#ifndef OPENMW_APPS_OPENMW_MWWORLD_MOVEMENTDIRECTION_H
#define OPENMW_APPS_OPENMW_MWWORLD_MOVEMENTDIRECTION_H
namespace MWWorld
{
using MovementDirectionFlags = unsigned char;
enum MovementDirectionFlag : MovementDirectionFlags
{
MovementDirectionFlag_Forward = 1 << 0,
MovementDirectionFlag_Back = 1 << 1,
MovementDirectionFlag_Left = 1 << 2,
MovementDirectionFlag_Right = 1 << 3,
};
}
#endif

@ -44,6 +44,8 @@ namespace SceneUtil
bool hasGroupStart(std::string_view groupName) const { return mGroups.count(groupName) > 0; }
const std::set<std::string, std::less<>>& getGroups() const { return mGroups; }
private:
struct IsGroupStart
{

Loading…
Cancel
Save