mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-02-20 20:09:41 +00:00
Merge pull request #2552 from akortunov/greeting
Split greetings from AiWander
This commit is contained in:
commit
ccb557edf0
8 changed files with 204 additions and 109 deletions
|
@ -32,6 +32,7 @@
|
|||
Bug #4449: Value returned by GetWindSpeed is incorrect
|
||||
Bug #4456: AiActivate should not be cancelled after target activation
|
||||
Bug #4540: Rain delay when exiting water
|
||||
Bug #4594: Actors without AI packages don't use Hello dialogue
|
||||
Bug #4600: Crash when no sound output is available or --no-sound is used.
|
||||
Bug #4639: Black screen after completing first mages guild mission + training
|
||||
Bug #4650: Focus is lost after pressing ESC in confirmation dialog inside savegame dialog
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "../mwrender/vismask.hpp"
|
||||
|
||||
#include "spellcasting.hpp"
|
||||
#include "steering.hpp"
|
||||
#include "npcstats.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
#include "movement.hpp"
|
||||
|
@ -141,6 +142,9 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
static const int GREETING_SHOULD_START = 4; //how many updates should pass before NPC can greet player
|
||||
static const int GREETING_SHOULD_END = 10;
|
||||
|
||||
class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor
|
||||
{
|
||||
public:
|
||||
|
@ -397,6 +401,113 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
|
||||
}
|
||||
|
||||
void Actors::updateGreetingState(const MWWorld::Ptr& actor, bool turnOnly)
|
||||
{
|
||||
if (!actor.getClass().isActor() || actor == getPlayer())
|
||||
return;
|
||||
|
||||
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
|
||||
int hello = stats.getAiSetting(CreatureStats::AI_Hello).getModified();
|
||||
if (hello == 0)
|
||||
return;
|
||||
|
||||
if (MWBase::Environment::get().getWorld()->isSwimming(actor))
|
||||
return;
|
||||
|
||||
MWWorld::Ptr player = getPlayer();
|
||||
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
|
||||
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||
osg::Vec3f dir = playerPos - actorPos;
|
||||
|
||||
const MWMechanics::AiSequence& seq = stats.getAiSequence();
|
||||
int packageId = seq.getTypeId();
|
||||
|
||||
if (seq.isInCombat() || (packageId != AiPackage::TypeIdWander && packageId != AiPackage::TypeIdTravel && packageId != -1))
|
||||
{
|
||||
stats.setTurningToPlayer(false);
|
||||
stats.setGreetingTimer(0);
|
||||
stats.setGreetingState(Greet_None);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stats.isTurningToPlayer())
|
||||
{
|
||||
// Reduce the turning animation glitch by using a *HUGE* value of
|
||||
// epsilon... TODO: a proper fix might be in either the physics or the
|
||||
// animation subsystem
|
||||
if (zTurn(actor, stats.getAngleToPlayer(), osg::DegreesToRadians(5.f)))
|
||||
{
|
||||
stats.setTurningToPlayer(false);
|
||||
// An original engine launches an endless idle2 when an actor greets player.
|
||||
playAnimationGroup (actor, "idle2", 0, std::numeric_limits<int>::max(), false);
|
||||
}
|
||||
}
|
||||
|
||||
if (turnOnly)
|
||||
return;
|
||||
|
||||
// Play a random voice greeting if the player gets too close
|
||||
float helloDistance = static_cast<float>(hello);
|
||||
static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore()
|
||||
.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->mValue.getInteger();
|
||||
|
||||
helloDistance *= iGreetDistanceMultiplier;
|
||||
|
||||
int greetingTimer = stats.getGreetingTimer();
|
||||
GreetingState greetingState = stats.getGreetingState();
|
||||
if (greetingState == Greet_None)
|
||||
{
|
||||
if ((playerPos - actorPos).length2() <= helloDistance*helloDistance &&
|
||||
!player.getClass().getCreatureStats(player).isDead() && !actor.getClass().getCreatureStats(actor).isParalyzed()
|
||||
&& MWBase::Environment::get().getWorld()->getLOS(player, actor)
|
||||
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
|
||||
greetingTimer++;
|
||||
|
||||
if (greetingTimer >= GREETING_SHOULD_START)
|
||||
{
|
||||
greetingState = Greet_InProgress;
|
||||
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
|
||||
greetingTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (greetingState == Greet_InProgress)
|
||||
{
|
||||
greetingTimer++;
|
||||
|
||||
turnActorToFacePlayer(actor, dir);
|
||||
|
||||
if (greetingTimer >= GREETING_SHOULD_END)
|
||||
{
|
||||
greetingState = Greet_Done;
|
||||
greetingTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (greetingState == Greet_Done)
|
||||
{
|
||||
float resetDist = 2 * helloDistance;
|
||||
if ((playerPos - actorPos).length2() >= resetDist*resetDist)
|
||||
greetingState = Greet_None;
|
||||
}
|
||||
|
||||
stats.setGreetingTimer(greetingTimer);
|
||||
stats.setGreetingState(greetingState);
|
||||
}
|
||||
|
||||
void Actors::turnActorToFacePlayer(const MWWorld::Ptr& actor, const osg::Vec3f& dir)
|
||||
{
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
|
||||
|
||||
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
|
||||
if (!stats.isTurningToPlayer())
|
||||
{
|
||||
stats.setAngleToPlayer(std::atan2(dir.x(), dir.y()));
|
||||
stats.setTurningToPlayer(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr> >& cachedAllies, bool againstPlayer)
|
||||
{
|
||||
// No combat for totally static creatures
|
||||
|
@ -1409,11 +1520,13 @@ namespace MWMechanics
|
|||
static float timerUpdateAITargets = 0;
|
||||
static float timerUpdateHeadTrack = 0;
|
||||
static float timerUpdateEquippedLight = 0;
|
||||
static float timerUpdateHello = 0;
|
||||
const float updateEquippedLightInterval = 1.0f;
|
||||
|
||||
// target lists get updated once every 1.0 sec
|
||||
if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0;
|
||||
if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0;
|
||||
if (timerUpdateHello >= 0.25f) timerUpdateHello = 0;
|
||||
if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0;
|
||||
if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
|
||||
|
||||
|
@ -1532,6 +1645,7 @@ namespace MWMechanics
|
|||
if (isConscious(iter->first))
|
||||
{
|
||||
stats.getAiSequence().execute(iter->first, *ctrl, duration);
|
||||
updateGreetingState(iter->first, timerUpdateHello > 0);
|
||||
playIdleDialogue(iter->first);
|
||||
}
|
||||
}
|
||||
|
@ -1554,6 +1668,7 @@ namespace MWMechanics
|
|||
timerUpdateAITargets += duration;
|
||||
timerUpdateHeadTrack += duration;
|
||||
timerUpdateEquippedLight += duration;
|
||||
timerUpdateHello += duration;
|
||||
mTimerDisposeSummonsCorpses += duration;
|
||||
|
||||
// Animation/movement update
|
||||
|
|
|
@ -121,6 +121,8 @@ namespace MWMechanics
|
|||
void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr> >& cachedAllies, bool againstPlayer);
|
||||
|
||||
void playIdleDialogue(const MWWorld::Ptr& actor);
|
||||
void updateGreetingState(const MWWorld::Ptr& actor, bool turnOnly);
|
||||
void turnActorToFacePlayer(const MWWorld::Ptr& actor, const osg::Vec3f& dir);
|
||||
|
||||
void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor,
|
||||
MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance);
|
||||
|
|
|
@ -43,11 +43,16 @@ namespace MWMechanics
|
|||
|
||||
bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
|
||||
{
|
||||
auto& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
if (stats.isTurningToPlayer() || stats.getGreetingState() == Greet_InProgress)
|
||||
return false;
|
||||
|
||||
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||
const osg::Vec3f targetPos(mX, mY, mZ);
|
||||
|
||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
|
||||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||
stats.setMovementFlag(CreatureStats::Flag_Run, false);
|
||||
stats.setDrawState(DrawState_Nothing);
|
||||
|
||||
if (!isWithinMaxRange(targetPos, actorPos))
|
||||
return false;
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
#include "pathgrid.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
#include "steering.hpp"
|
||||
#include "movement.hpp"
|
||||
#include "coordinateconverter.hpp"
|
||||
#include "actorutil.hpp"
|
||||
|
@ -26,8 +25,6 @@ namespace MWMechanics
|
|||
{
|
||||
static const int COUNT_BEFORE_RESET = 10;
|
||||
static const float DOOR_CHECK_INTERVAL = 1.5f;
|
||||
static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player
|
||||
static const int GREETING_SHOULD_END = 10;
|
||||
|
||||
// to prevent overcrowding
|
||||
static const int DESTINATION_TOLERANCE = 64;
|
||||
|
@ -176,6 +173,17 @@ namespace MWMechanics
|
|||
storage.setState(AiWanderStorage::Wander_Walking);
|
||||
}
|
||||
|
||||
GreetingState greetingState = cStats.getGreetingState();
|
||||
if (greetingState == Greet_InProgress)
|
||||
{
|
||||
if (storage.mState == AiWanderStorage::Wander_Walking)
|
||||
{
|
||||
stopWalking(actor, storage, false);
|
||||
mObstacleCheck.clear();
|
||||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||
}
|
||||
}
|
||||
|
||||
doPerFrameActionsForState(actor, duration, storage);
|
||||
|
||||
float& lastReaction = storage.mReaction;
|
||||
|
@ -245,13 +253,7 @@ namespace MWMechanics
|
|||
if(mDistance && cellChange)
|
||||
mDistance = 0;
|
||||
|
||||
// Allow interrupting a walking actor to trigger a greeting
|
||||
AiWanderStorage::WanderState& wanderState = storage.mState;
|
||||
if ((wanderState == AiWanderStorage::Wander_IdleNow) || (wanderState == AiWanderStorage::Wander_Walking))
|
||||
{
|
||||
playGreetingIfPlayerGetsTooClose(actor, storage);
|
||||
}
|
||||
|
||||
if ((wanderState == AiWanderStorage::Wander_MoveNow) && storage.mCanWanderAlongPathGrid)
|
||||
{
|
||||
// Construct a new path if there isn't one
|
||||
|
@ -416,19 +418,9 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
bool& rotate = storage.mTurnActorGivingGreetingToFacePlayer;
|
||||
if (rotate)
|
||||
{
|
||||
// Reduce the turning animation glitch by using a *HUGE* value of
|
||||
// epsilon... TODO: a proper fix might be in either the physics or the
|
||||
// animation subsystem
|
||||
if (zTurn(actor, storage.mTargetAngleRadians, osg::DegreesToRadians(5.f)))
|
||||
rotate = false;
|
||||
}
|
||||
|
||||
// Check if idle animation finished
|
||||
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
|
||||
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == AiWanderStorage::Greet_Done || greetingState == AiWanderStorage::Greet_None))
|
||||
GreetingState greetingState = actor.getClass().getCreatureStats(actor).getGreetingState();
|
||||
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None))
|
||||
{
|
||||
if (mPathFinder.isPathConstructed())
|
||||
storage.setState(AiWanderStorage::Wander_Walking);
|
||||
|
@ -517,74 +509,7 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void AiWander::playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||
{
|
||||
// Play a random voice greeting if the player gets too close
|
||||
int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
|
||||
float helloDistance = static_cast<float>(hello);
|
||||
static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore()
|
||||
.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->mValue.getInteger();
|
||||
|
||||
helloDistance *= iGreetDistanceMultiplier;
|
||||
|
||||
MWWorld::Ptr player = getPlayer();
|
||||
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
|
||||
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||
|
||||
int& greetingTimer = storage.mGreetingTimer;
|
||||
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
|
||||
if (greetingState == AiWanderStorage::Greet_None)
|
||||
{
|
||||
if ((playerPos - actorPos).length2() <= helloDistance*helloDistance &&
|
||||
!player.getClass().getCreatureStats(player).isDead() && !actor.getClass().getCreatureStats(actor).isParalyzed()
|
||||
&& MWBase::Environment::get().getWorld()->getLOS(player, actor)
|
||||
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
|
||||
greetingTimer++;
|
||||
|
||||
if (greetingTimer >= GREETING_SHOULD_START)
|
||||
{
|
||||
greetingState = AiWanderStorage::Greet_InProgress;
|
||||
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
|
||||
greetingTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (greetingState == AiWanderStorage::Greet_InProgress)
|
||||
{
|
||||
greetingTimer++;
|
||||
|
||||
if (storage.mState == AiWanderStorage::Wander_Walking)
|
||||
{
|
||||
stopWalking(actor, storage, false);
|
||||
mObstacleCheck.clear();
|
||||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||
}
|
||||
|
||||
turnActorToFacePlayer(actorPos, playerPos, storage);
|
||||
|
||||
if (greetingTimer >= GREETING_SHOULD_END)
|
||||
{
|
||||
greetingState = AiWanderStorage::Greet_Done;
|
||||
greetingTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (greetingState == AiWanderStorage::Greet_Done)
|
||||
{
|
||||
float resetDist = 2 * helloDistance;
|
||||
if ((playerPos - actorPos).length2() >= resetDist*resetDist)
|
||||
greetingState = AiWanderStorage::Greet_None;
|
||||
}
|
||||
}
|
||||
|
||||
void AiWander::turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage)
|
||||
{
|
||||
osg::Vec3f dir = playerPosition - actorPosition;
|
||||
|
||||
float faceAngleRadians = std::atan2(dir.x(), dir.y());
|
||||
storage.mTargetAngleRadians = faceAngleRadians;
|
||||
storage.mTurnActorGivingGreetingToFacePlayer = true;
|
||||
}
|
||||
|
||||
void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
|
||||
{
|
||||
|
|
|
@ -25,21 +25,8 @@ namespace MWMechanics
|
|||
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
|
||||
struct AiWanderStorage : AiTemporaryBase
|
||||
{
|
||||
// the z rotation angle to reach
|
||||
// when mTurnActorGivingGreetingToFacePlayer is true
|
||||
float mTargetAngleRadians;
|
||||
bool mTurnActorGivingGreetingToFacePlayer;
|
||||
float mReaction; // update some actions infrequently
|
||||
|
||||
enum GreetingState
|
||||
{
|
||||
Greet_None,
|
||||
Greet_InProgress,
|
||||
Greet_Done
|
||||
};
|
||||
GreetingState mSaidGreeting;
|
||||
int mGreetingTimer;
|
||||
|
||||
const MWWorld::CellStore* mCell; // for detecting cell change
|
||||
|
||||
// AiWander states
|
||||
|
@ -72,11 +59,7 @@ namespace MWMechanics
|
|||
int mStuckCount;
|
||||
|
||||
AiWanderStorage():
|
||||
mTargetAngleRadians(0),
|
||||
mTurnActorGivingGreetingToFacePlayer(false),
|
||||
mReaction(0),
|
||||
mSaidGreeting(Greet_None),
|
||||
mGreetingTimer(0),
|
||||
mCell(nullptr),
|
||||
mState(Wander_ChooseAction),
|
||||
mIsWanderingManually(false),
|
||||
|
@ -136,7 +119,6 @@ namespace MWMechanics
|
|||
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||
short unsigned getRandomIdle();
|
||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
void evadeObstacles(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||
void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
|
||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||
|
|
|
@ -23,12 +23,53 @@ namespace MWMechanics
|
|||
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
|
||||
mHitRecovery(false), mBlock(false), mMovementFlags(0),
|
||||
mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1),
|
||||
mDeathAnimation(-1), mTimeOfDeath(), mLevel (0)
|
||||
mDeathAnimation(-1), mTimeOfDeath(), mGreetingState(Greet_None),
|
||||
mGreetingTimer(0), mTargetAngleRadians(0), mIsTurningToPlayer(false), mLevel (0)
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mAiSettings[i] = 0;
|
||||
}
|
||||
|
||||
int MWMechanics::CreatureStats::getGreetingTimer() const
|
||||
{
|
||||
return mGreetingTimer;
|
||||
}
|
||||
|
||||
void MWMechanics::CreatureStats::setGreetingTimer(int timer)
|
||||
{
|
||||
mGreetingTimer = timer;
|
||||
}
|
||||
|
||||
float MWMechanics::CreatureStats::getAngleToPlayer() const
|
||||
{
|
||||
return mTargetAngleRadians;
|
||||
}
|
||||
|
||||
void MWMechanics::CreatureStats::setAngleToPlayer(float angle)
|
||||
{
|
||||
mTargetAngleRadians = angle;
|
||||
}
|
||||
|
||||
GreetingState MWMechanics::CreatureStats::getGreetingState() const
|
||||
{
|
||||
return mGreetingState;
|
||||
}
|
||||
|
||||
void MWMechanics::CreatureStats::setGreetingState(GreetingState state)
|
||||
{
|
||||
mGreetingState = state;
|
||||
}
|
||||
|
||||
bool MWMechanics::CreatureStats::isTurningToPlayer() const
|
||||
{
|
||||
return mIsTurningToPlayer;
|
||||
}
|
||||
|
||||
void MWMechanics::CreatureStats::setTurningToPlayer(bool turning)
|
||||
{
|
||||
mIsTurningToPlayer = turning;
|
||||
}
|
||||
|
||||
const AiSequence& CreatureStats::getAiSequence() const
|
||||
{
|
||||
return mAiSequence;
|
||||
|
|
|
@ -19,6 +19,13 @@ namespace ESM
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
enum GreetingState
|
||||
{
|
||||
Greet_None,
|
||||
Greet_InProgress,
|
||||
Greet_Done
|
||||
};
|
||||
|
||||
/// \brief Common creature stats
|
||||
///
|
||||
///
|
||||
|
@ -70,6 +77,11 @@ namespace MWMechanics
|
|||
|
||||
MWWorld::TimeStamp mTimeOfDeath;
|
||||
|
||||
GreetingState mGreetingState;
|
||||
int mGreetingTimer;
|
||||
float mTargetAngleRadians;
|
||||
bool mIsTurningToPlayer;
|
||||
|
||||
public:
|
||||
typedef std::pair<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID>
|
||||
private:
|
||||
|
@ -85,6 +97,18 @@ namespace MWMechanics
|
|||
public:
|
||||
CreatureStats();
|
||||
|
||||
int getGreetingTimer() const;
|
||||
void setGreetingTimer(int timer);
|
||||
|
||||
float getAngleToPlayer() const;
|
||||
void setAngleToPlayer(float angle);
|
||||
|
||||
GreetingState getGreetingState() const;
|
||||
void setGreetingState(GreetingState state);
|
||||
|
||||
bool isTurningToPlayer() const;
|
||||
void setTurningToPlayer(bool turning);
|
||||
|
||||
DrawState_ getDrawState() const;
|
||||
void setDrawState(DrawState_ state);
|
||||
|
||||
|
|
Loading…
Reference in a new issue