Merged pull request #1477

0.6.3
Marc Zinnschlag 7 years ago
commit 7d9de93fd3

@ -1,16 +1,20 @@
0.45.0 0.45.0
------ ------
Bug #2835: Player able to slowly move when overencumbered Bug #2835: Player able to slowly move when overencumbered
Bug #3374: Touch spells not hitting kwama foragers Bug #3374: Touch spells not hitting kwama foragers
Bug #3591: Angled hit distance too low Bug #3591: Angled hit distance too low
Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #3897: Have Goodbye give all choices the effects of Goodbye
Bug #3997: Almalexia doesn't pace
Bug #4036: Weird behaviour of AI packages if package target has non-unique ID Bug #4036: Weird behaviour of AI packages if package target has non-unique ID
Bug #4221: Characters get stuck in V-shaped terrain Bug #4221: Characters get stuck in V-shaped terrain
Bug #4251: Stationary NPCs do not return to their position after combat
Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4293: Faction members are not aware of faction ownerships in barter
Bug #4327: Missing animations during spell/weapon stance switching Bug #4327: Missing animations during spell/weapon stance switching
Bug #4419: MRK NiStringExtraData is handled incorrectly Bug #4419: MRK NiStringExtraData is handled incorrectly
Bug #4426: RotateWorld behavior is incorrect Bug #4426: RotateWorld behavior is incorrect
Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2
Bug #4432: Guards behaviour is incorrect if they do not have AI packages
Bug #4433: Guard behaviour is incorrect with Alarm = 0 Bug #4433: Guard behaviour is incorrect with Alarm = 0
Feature #4444: Per-group KF-animation files support Feature #4444: Per-group KF-animation files support

@ -48,7 +48,8 @@ namespace MWMechanics
TypeIdPursue = 6, TypeIdPursue = 6,
TypeIdAvoidDoor = 7, TypeIdAvoidDoor = 7,
TypeIdFace = 8, TypeIdFace = 8,
TypeIdBreathe = 9 TypeIdBreathe = 9,
TypeIdInternalTravel = 10
}; };
///Default constructor ///Default constructor
@ -79,6 +80,9 @@ namespace MWMechanics
/// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr)
virtual MWWorld::Ptr getTarget() const; virtual MWWorld::Ptr getTarget() const;
/// Get the destination point of the AI package (not applicable to all AI packages, default return (0, 0, 0))
virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); };
/// Return true if having this AiPackage makes the actor side with the target in fights (default false) /// Return true if having this AiPackage makes the actor side with the target in fights (default false)
virtual bool sideWithTarget() const; virtual bool sideWithTarget() const;

@ -184,7 +184,8 @@ bool isActualAiPackage(int packageTypeId)
&& packageTypeId != AiPackage::TypeIdPursue && packageTypeId != AiPackage::TypeIdPursue
&& packageTypeId != AiPackage::TypeIdAvoidDoor && packageTypeId != AiPackage::TypeIdAvoidDoor
&& packageTypeId != AiPackage::TypeIdFace && packageTypeId != AiPackage::TypeIdFace
&& packageTypeId != AiPackage::TypeIdBreathe); && packageTypeId != AiPackage::TypeIdBreathe
&& packageTypeId != AiPackage::TypeIdInternalTravel);
} }
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
@ -298,7 +299,7 @@ void AiSequence::clear()
mPackages.clear(); mPackages.clear();
} }
void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther)
{ {
if (actor == getPlayer()) if (actor == getPlayer())
throw std::runtime_error("Can't add AI packages to player"); throw std::runtime_error("Can't add AI packages to player");
@ -307,8 +308,33 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor)
if (isActualAiPackage(package.getTypeId())) if (isActualAiPackage(package.getTypeId()))
stopCombat(); stopCombat();
// We should return a wandering actor back after combat or pursuit.
// The same thing for actors without AI packages.
// Also there is no point to stack return packages.
int currentTypeId = getTypeId();
int newTypeId = package.getTypeId();
if (currentTypeId <= MWMechanics::AiPackage::TypeIdWander
&& !hasPackage(MWMechanics::AiPackage::TypeIdInternalTravel)
&& (newTypeId <= MWMechanics::AiPackage::TypeIdCombat
|| newTypeId == MWMechanics::AiPackage::TypeIdPursue))
{
osg::Vec3f dest;
if (currentTypeId == MWMechanics::AiPackage::TypeIdWander)
{
AiPackage* activePackage = getActivePackage();
dest = activePackage->getDestination(actor);
}
else
{
dest = actor.getRefData().getPosition().asVec3();
}
MWMechanics::AiTravel travelPackage(dest.x(), dest.y(), dest.z(), true);
stack(travelPackage, actor, false);
}
// remove previous packages if required // remove previous packages if required
if (package.shouldCancelPreviousAi()) if (cancelOther && package.shouldCancelPreviousAi())
{ {
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end();) for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end();)
{ {
@ -392,6 +418,8 @@ void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const
{ {
(*iter)->writeState(sequence); (*iter)->writeState(sequence);
} }
sequence.mLastAiPackage = mLastAiPackage;
} }
void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
@ -403,7 +431,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
int count = 0; int count = 0;
for (std::vector<ESM::AiSequence::AiPackageContainer>::const_iterator it = sequence.mPackages.begin(); for (std::vector<ESM::AiSequence::AiPackageContainer>::const_iterator it = sequence.mPackages.begin();
it != sequence.mPackages.end(); ++it) it != sequence.mPackages.end(); ++it)
{ {
if (isActualAiPackage(it->mType)) if (isActualAiPackage(it->mType))
count++; count++;
} }
@ -462,6 +490,8 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
mPackages.push_back(package.release()); mPackages.push_back(package.release());
} }
mLastAiPackage = sequence.mLastAiPackage;
} }
void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state) void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state)

@ -115,7 +115,7 @@ namespace MWMechanics
///< Add \a package to the front of the sequence ///< Add \a package to the front of the sequence
/** Suspends current package /** Suspends current package
@param actor The actor that owns this AiSequence **/ @param actor The actor that owns this AiSequence **/
void stack (const AiPackage& package, const MWWorld::Ptr& actor); void stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther=true);
/// Return the current active package. /// Return the current active package.
/** If there is no active package, it will throw an exception **/ /** If there is no active package, it will throw an exception **/

@ -28,15 +28,14 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2)
namespace MWMechanics namespace MWMechanics
{ {
AiTravel::AiTravel(float x, float y, float z) AiTravel::AiTravel(float x, float y, float z, bool hidden)
: mX(x),mY(y),mZ(z) : mX(x),mY(y),mZ(z),mHidden(hidden)
{ {
} }
AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel)
: mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ) : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(travel->mHidden)
{ {
} }
AiTravel *MWMechanics::AiTravel::clone() const AiTravel *MWMechanics::AiTravel::clone() const
@ -64,7 +63,7 @@ namespace MWMechanics
int AiTravel::getTypeId() const int AiTravel::getTypeId() const
{ {
return TypeIdTravel; return mHidden ? TypeIdInternalTravel : TypeIdTravel;
} }
void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state)
@ -83,6 +82,7 @@ namespace MWMechanics
travel->mData.mX = mX; travel->mData.mX = mX;
travel->mData.mY = mY; travel->mData.mY = mY;
travel->mData.mZ = mZ; travel->mData.mZ = mZ;
travel->mHidden = mHidden;
ESM::AiSequence::AiPackageContainer package; ESM::AiSequence::AiPackageContainer package;
package.mType = ESM::AiSequence::Ai_Travel; package.mType = ESM::AiSequence::Ai_Travel;

@ -20,7 +20,7 @@ namespace MWMechanics
{ {
public: public:
/// Default constructor /// Default constructor
AiTravel(float x, float y, float z); AiTravel(float x, float y, float z, bool hidden = false);
AiTravel(const ESM::AiSequence::AiTravel* travel); AiTravel(const ESM::AiSequence::AiTravel* travel);
/// Simulates the passing of time /// Simulates the passing of time
@ -38,6 +38,8 @@ namespace MWMechanics
float mX; float mX;
float mY; float mY;
float mZ; float mZ;
bool mHidden;
}; };
} }

@ -59,7 +59,7 @@ namespace MWMechanics
float mTargetAngleRadians; float mTargetAngleRadians;
bool mTurnActorGivingGreetingToFacePlayer; bool mTurnActorGivingGreetingToFacePlayer;
float mReaction; // update some actions infrequently float mReaction; // update some actions infrequently
AiWander::GreetingState mSaidGreeting; AiWander::GreetingState mSaidGreeting;
int mGreetingTimer; int mGreetingTimer;
@ -70,7 +70,7 @@ namespace MWMechanics
bool mIsWanderingManually; bool mIsWanderingManually;
bool mCanWanderAlongPathGrid; bool mCanWanderAlongPathGrid;
unsigned short mIdleAnimation; unsigned short mIdleAnimation;
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
@ -86,7 +86,7 @@ namespace MWMechanics
float mDoorCheckDuration; float mDoorCheckDuration;
int mStuckCount; int mStuckCount;
AiWanderStorage(): AiWanderStorage():
mTargetAngleRadians(0), mTargetAngleRadians(0),
mTurnActorGivingGreetingToFacePlayer(false), mTurnActorGivingGreetingToFacePlayer(false),
@ -111,7 +111,7 @@ namespace MWMechanics
mIsWanderingManually = isManualWander; mIsWanderingManually = isManualWander;
} }
}; };
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat): AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)) mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0))
@ -223,7 +223,7 @@ namespace MWMechanics
if (mPathFinder.isPathConstructed()) if (mPathFinder.isPathConstructed())
storage.setState(Wander_Walking); storage.setState(Wander_Walking);
} }
doPerFrameActionsForState(actor, duration, storage, pos); doPerFrameActionsForState(actor, duration, storage, pos);
playIdleDialogueRandomly(actor); playIdleDialogueRandomly(actor);
@ -298,13 +298,6 @@ namespace MWMechanics
if(mDistance && cellChange) if(mDistance && cellChange)
mDistance = 0; mDistance = 0;
// For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere
if (mDistance == 0 && !cellChange
&& (pos.asVec3() - mInitialActorPosition).length2() > (DESTINATION_TOLERANCE * DESTINATION_TOLERANCE))
{
returnToStartLocation(actor, storage, pos);
}
// Allow interrupting a walking actor to trigger a greeting // Allow interrupting a walking actor to trigger a greeting
WanderState& wanderState = storage.mState; WanderState& wanderState = storage.mState;
if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking)) if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking))
@ -321,7 +314,7 @@ namespace MWMechanics
{ {
setPathToAnAllowedNode(actor, storage, pos); setPathToAnAllowedNode(actor, storage, pos);
} }
} }
} else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { } else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) {
completeManualWalking(actor, storage); completeManualWalking(actor, storage);
} }
@ -330,10 +323,19 @@ namespace MWMechanics
} }
bool AiWander::getRepeat() const bool AiWander::getRepeat() const
{ {
return mRepeat; return mRepeat;
} }
osg::Vec3f AiWander::getDestination(const MWWorld::Ptr& actor) const
{
if (mHasDestination)
return mDestination;
const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos;
const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ);
return currentPositionVec3f;
}
bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage) bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage)
{ {
@ -342,35 +344,14 @@ namespace MWMechanics
// End package if duration is complete // End package if duration is complete
if (mRemainingDuration <= 0) if (mRemainingDuration <= 0)
{ {
stopWalking(actor, storage); stopWalking(actor, storage);
return true; return true;
} }
} }
// if get here, not yet completed // if get here, not yet completed
return false; return false;
} }
void AiWander::returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
{
if (!mPathFinder.isPathConstructed())
{
mDestination = mInitialActorPosition;
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination));
// actor position is already in world coordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
// don't take shortcuts for wandering
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mPathFinder.isPathConstructed())
{
storage.setState(Wander_Walking);
mHasDestination = true;
}
}
}
/* /*
* Commands actor to walk to a random location near original spawn location. * Commands actor to walk to a random location near original spawn location.
*/ */
@ -497,7 +478,7 @@ namespace MWMechanics
} }
} }
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor,
float duration, AiWanderStorage& storage, ESM::Position& pos) float duration, AiWanderStorage& storage, ESM::Position& pos)
{ {
// Is there no destination or are we there yet? // Is there no destination or are we there yet?
@ -873,7 +854,7 @@ namespace MWMechanics
state.moveIn(new AiWanderStorage()); state.moveIn(new AiWanderStorage());
MWBase::Environment::get().getWorld()->moveObject(actor, static_cast<float>(dest.mX), MWBase::Environment::get().getWorld()->moveObject(actor, static_cast<float>(dest.mX),
static_cast<float>(dest.mY), static_cast<float>(dest.mZ)); static_cast<float>(dest.mY), static_cast<float>(dest.mZ));
actor.getClass().adjustPosition(actor, false); actor.getClass().adjustPosition(actor, false);
} }
@ -914,7 +895,7 @@ namespace MWMechanics
// get NPC's position in local (i.e. cell) coordinates // get NPC's position in local (i.e. cell) coordinates
osg::Vec3f npcPos(mInitialActorPosition); osg::Vec3f npcPos(mInitialActorPosition);
CoordinateConverter(cell).toLocal(npcPos); CoordinateConverter(cell).toLocal(npcPos);
// Find closest pathgrid point // Find closest pathgrid point
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos); int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos);
@ -945,7 +926,7 @@ namespace MWMechanics
storage.mPopulateAvailableNodes = false; storage.mPopulateAvailableNodes = false;
} }
// When only one path grid point in wander distance, // When only one path grid point in wander distance,
// additional points for NPC to wander to are: // additional points for NPC to wander to are:
// 1. NPC's initial location // 1. NPC's initial location
// 2. Partway along the path between the point and its connected points. // 2. Partway along the path between the point and its connected points.
@ -969,7 +950,7 @@ namespace MWMechanics
delta.normalize(); delta.normalize();
int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE); int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE);
// must not travel longer than distance between waypoints or NPC goes past waypoint // must not travel longer than distance between waypoints or NPC goes past waypoint
distance = std::min(distance, static_cast<int>(length)); distance = std::min(distance, static_cast<int>(length));
delta *= distance; delta *= distance;
@ -1041,4 +1022,3 @@ namespace MWMechanics
init(); init();
} }
} }

@ -47,9 +47,11 @@ namespace MWMechanics
virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; virtual void writeState(ESM::AiSequence::AiSequence &sequence) const;
virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); virtual void fastForward(const MWWorld::Ptr& actor, AiState& state);
bool getRepeat() const; bool getRepeat() const;
osg::Vec3f getDestination(const MWWorld::Ptr& actor) const;
enum GreetingState { enum GreetingState {
Greet_None, Greet_None,
Greet_InProgress, Greet_InProgress,
@ -85,7 +87,6 @@ namespace MWMechanics
bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration); const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration);
bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos);
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination); bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination);

@ -22,6 +22,7 @@
#include "aicombat.hpp" #include "aicombat.hpp"
#include "aipursue.hpp" #include "aipursue.hpp"
#include "aitravel.hpp"
#include "spellcasting.hpp" #include "spellcasting.hpp"
#include "autocalcspell.hpp" #include "autocalcspell.hpp"
#include "npcstats.hpp" #include "npcstats.hpp"
@ -1598,9 +1599,12 @@ namespace MWMechanics
void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target)
{ {
if (ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(target)) MWMechanics::AiSequence& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence();
if (aiSequence.isInCombat(target))
return; return;
ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr);
aiSequence.stack(MWMechanics::AiCombat(target), ptr);
if (target == getPlayer()) if (target == getPlayer())
{ {
// if guard starts combat with player, guards pursuing player should do the same // if guard starts combat with player, guards pursuing player should do the same

@ -35,11 +35,13 @@ namespace AiSequence
void AiTravel::load(ESMReader &esm) void AiTravel::load(ESMReader &esm)
{ {
esm.getHNT (mData, "DATA"); esm.getHNT (mData, "DATA");
esm.getHNOT (mHidden, "HIDD");
} }
void AiTravel::save(ESMWriter &esm) const void AiTravel::save(ESMWriter &esm) const
{ {
esm.writeHNT ("DATA", mData); esm.writeHNT ("DATA", mData);
esm.writeHNT ("HIDD", mHidden);
} }
void AiEscort::load(ESMReader &esm) void AiEscort::load(ESMReader &esm)
@ -158,6 +160,8 @@ namespace AiSequence
break; break;
} }
} }
esm.writeHNT ("LAST", mLastAiPackage);
} }
void AiSequence::load(ESMReader &esm) void AiSequence::load(ESMReader &esm)
@ -225,6 +229,8 @@ namespace AiSequence
return; return;
} }
} }
esm.getHNOT (mLastAiPackage, "LAST");
} }
} }
} }

@ -80,6 +80,7 @@ namespace ESM
struct AiTravel : AiPackage struct AiTravel : AiPackage
{ {
AiTravelData mData; AiTravelData mData;
bool mHidden;
void load(ESMReader &esm); void load(ESMReader &esm);
void save(ESMWriter &esm) const; void save(ESMWriter &esm) const;
@ -149,10 +150,14 @@ namespace ESM
struct AiSequence struct AiSequence
{ {
AiSequence() {} AiSequence()
{
mLastAiPackage = -1;
}
~AiSequence(); ~AiSequence();
std::vector<AiPackageContainer> mPackages; std::vector<AiPackageContainer> mPackages;
int mLastAiPackage;
void load (ESMReader &esm); void load (ESMReader &esm);
void save (ESMWriter &esm) const; void save (ESMWriter &esm) const;

Loading…
Cancel
Save