Split greetings from AiWander (bug #4594)

pull/556/head
Andrei Kortunov 5 years ago
parent b7a1e6561b
commit 69aceb5c1e

@ -31,6 +31,7 @@
Bug #4411: Reloading a saved game while falling prevents damage in some cases
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…
Cancel
Save