Merged pull request #1477

pull/456/head
Marc Zinnschlag 6 years ago
commit 7d9de93fd3

@ -1,16 +1,20 @@
0.45.0
------
Bug #2835: Player able to slowly move when overencumbered
Bug #3374: Touch spells not hitting kwama foragers
Bug #3591: Angled hit distance too low
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 #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 #4327: Missing animations during spell/weapon stance switching
Bug #4419: MRK NiStringExtraData is handled incorrectly
Bug #4426: RotateWorld behavior is incorrect
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
Feature #4444: Per-group KF-animation files support

@ -48,7 +48,8 @@ namespace MWMechanics
TypeIdPursue = 6,
TypeIdAvoidDoor = 7,
TypeIdFace = 8,
TypeIdBreathe = 9
TypeIdBreathe = 9,
TypeIdInternalTravel = 10
};
///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)
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)
virtual bool sideWithTarget() const;

@ -184,7 +184,8 @@ bool isActualAiPackage(int packageTypeId)
&& packageTypeId != AiPackage::TypeIdPursue
&& packageTypeId != AiPackage::TypeIdAvoidDoor
&& 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)
@ -298,7 +299,7 @@ void AiSequence::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())
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()))
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
if (package.shouldCancelPreviousAi())
if (cancelOther && package.shouldCancelPreviousAi())
{
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);
}
sequence.mLastAiPackage = mLastAiPackage;
}
void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
@ -403,7 +431,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
int count = 0;
for (std::vector<ESM::AiSequence::AiPackageContainer>::const_iterator it = sequence.mPackages.begin();
it != sequence.mPackages.end(); ++it)
{
{
if (isActualAiPackage(it->mType))
count++;
}
@ -462,6 +490,8 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
mPackages.push_back(package.release());
}
mLastAiPackage = sequence.mLastAiPackage;
}
void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state)

@ -115,7 +115,7 @@ namespace MWMechanics
///< Add \a package to the front of the sequence
/** Suspends current package
@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.
/** 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
{
AiTravel::AiTravel(float x, float y, float z)
: mX(x),mY(y),mZ(z)
AiTravel::AiTravel(float x, float y, float z, bool hidden)
: mX(x),mY(y),mZ(z),mHidden(hidden)
{
}
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
@ -64,7 +63,7 @@ namespace MWMechanics
int AiTravel::getTypeId() const
{
return TypeIdTravel;
return mHidden ? TypeIdInternalTravel : TypeIdTravel;
}
void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state)
@ -83,6 +82,7 @@ namespace MWMechanics
travel->mData.mX = mX;
travel->mData.mY = mY;
travel->mData.mZ = mZ;
travel->mHidden = mHidden;
ESM::AiSequence::AiPackageContainer package;
package.mType = ESM::AiSequence::Ai_Travel;

@ -20,7 +20,7 @@ namespace MWMechanics
{
public:
/// 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);
/// Simulates the passing of time
@ -38,6 +38,8 @@ namespace MWMechanics
float mX;
float mY;
float mZ;
bool mHidden;
};
}

@ -59,7 +59,7 @@ namespace MWMechanics
float mTargetAngleRadians;
bool mTurnActorGivingGreetingToFacePlayer;
float mReaction; // update some actions infrequently
AiWander::GreetingState mSaidGreeting;
int mGreetingTimer;
@ -70,7 +70,7 @@ namespace MWMechanics
bool mIsWanderingManually;
bool mCanWanderAlongPathGrid;
unsigned short mIdleAnimation;
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
@ -86,7 +86,7 @@ namespace MWMechanics
float mDoorCheckDuration;
int mStuckCount;
AiWanderStorage():
mTargetAngleRadians(0),
mTurnActorGivingGreetingToFacePlayer(false),
@ -111,7 +111,7 @@ namespace MWMechanics
mIsWanderingManually = isManualWander;
}
};
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),
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())
storage.setState(Wander_Walking);
}
doPerFrameActionsForState(actor, duration, storage, pos);
playIdleDialogueRandomly(actor);
@ -298,13 +298,6 @@ namespace MWMechanics
if(mDistance && cellChange)
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
WanderState& wanderState = storage.mState;
if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking))
@ -321,7 +314,7 @@ namespace MWMechanics
{
setPathToAnAllowedNode(actor, storage, pos);
}
}
}
} else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) {
completeManualWalking(actor, storage);
}
@ -330,10 +323,19 @@ namespace MWMechanics
}
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)
{
@ -342,35 +344,14 @@ namespace MWMechanics
// End package if duration is complete
if (mRemainingDuration <= 0)
{
stopWalking(actor, storage);
return true;
stopWalking(actor, storage);
return true;
}
}
// if get here, not yet completed
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.
*/
@ -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)
{
// Is there no destination or are we there yet?
@ -873,7 +854,7 @@ namespace MWMechanics
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));
actor.getClass().adjustPosition(actor, false);
}
@ -914,7 +895,7 @@ namespace MWMechanics
// get NPC's position in local (i.e. cell) coordinates
osg::Vec3f npcPos(mInitialActorPosition);
CoordinateConverter(cell).toLocal(npcPos);
// Find closest pathgrid point
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos);
@ -945,7 +926,7 @@ namespace MWMechanics
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:
// 1. NPC's initial location
// 2. Partway along the path between the point and its connected points.
@ -969,7 +950,7 @@ namespace MWMechanics
delta.normalize();
int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE);
// must not travel longer than distance between waypoints or NPC goes past waypoint
distance = std::min(distance, static_cast<int>(length));
delta *= distance;
@ -1041,4 +1022,3 @@ namespace MWMechanics
init();
}
}

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

@ -22,6 +22,7 @@
#include "aicombat.hpp"
#include "aipursue.hpp"
#include "aitravel.hpp"
#include "spellcasting.hpp"
#include "autocalcspell.hpp"
#include "npcstats.hpp"
@ -1598,9 +1599,12 @@ namespace MWMechanics
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;
ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr);
aiSequence.stack(MWMechanics::AiCombat(target), ptr);
if (target == getPlayer())
{
// if guard starts combat with player, guards pursuing player should do the same

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

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

Loading…
Cancel
Save