Add OpenMW commits up to 4 Feb 2020

# Conflicts:
#	.travis.yml
#	CI/before_script.linux.sh
pull/556/head
David Cernat 5 years ago
commit f0f76516d8

@ -10,10 +10,7 @@ env:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: "1QK0yVyoOB+gf2I7XzvhXu9w/5lq4stBXIwJbVCTjz4Q4XVHCosURaW1MAgKzMrPnbFEwjyn5uQ8BwsvvfkuN1AZD0YXITgc7gyI+J1wQ/p/ljxRxglakU6WEgsTs2J5z9UmGac4YTXg+quK7YP3rv+zuGim2I2rhzImejyzp0Ym3kRCnNcy+SGBsiRaevRJMe00Ch8zGAbEhduQGeSoS6W0rcu02DNlQKiq5NktWsXR+TWWWVfIeIlQR/lbPsCd0pdxMaMv2QCY0rVbwrYxWJwr/Qe45dAdWp+8/C3PbXpeMSGxlLa33nJNX4Lf/djxbjm8KWk6edaXPajrjR/0iwcpwq0jg2Jt6XfEdnJt35F1gpXlc04sxStjG45uloOKCFYT0wdhIO1Lq+hDP54wypQl+JInd5qC001O7pwhVxO36EgKWqo8HD+BqGDBwsNj2engy9Qcp3wO6G0rLBPB3CrZsk9wrHVv5cSiQSLMhId3Xviu3ZI2qEDA+kgTvxrKrsnMj4bILVCyG5Ka2Mj22wIDW9e8oIab9oTdujax3DTN1GkD6QuOAGzwDsNwGASsgfoeZ+FUhgM75RlBWGMilgkmnF7EJ0oAXLEpjtABnEr2d4qHv+y08kOuTDBLB9ExzCIj024dYYYNLZrqPKx0ncHuCMG2QNj2aJAJEZtj1rQ="
cache:
ccache: true
directories:
- ${HOME}/.ccache
cache: ccache
addons:
apt:
sources:
@ -70,8 +67,10 @@ matrix:
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
before_install:
- ./CI/before_install.${TRAVIS_OS_NAME}.sh
before_script: ./CI/before_script.${TRAVIS_OS_NAME}.sh
- ./CI/before_install.${TRAVIS_OS_NAME}.sh
before_script:
- ccache -z
- ./CI/before_script.${TRAVIS_OS_NAME}.sh
script:
- cd ./build
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j3; fi
@ -79,6 +78,8 @@ script:
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
- cd "${TRAVIS_BUILD_DIR}"
- ccache -s
#deploy:
# provider: script
# script: ./CI/deploy.osx.sh

@ -194,6 +194,8 @@
Bug #5242: ExplodeSpell behavior differs from Cast behavior
Bug #5249: Wandering NPCs start walking too soon after they hello
Bug #5250: Creatures display shield ground mesh instead of shield body part
Bug #5255: "GetTarget, player" doesn't return 1 during NPC hello
Bug #5261: Creatures can sometimes become stuck playing idles and never wander again
Feature #1774: Handle AvoidNode
Feature #2229: Improve pathfinding AI
Feature #3025: Analogue gamepad movement controls

@ -15,9 +15,7 @@ fi
export RAKNET_ROOT=~/CrabNet
export CODE_COVERAGE=1
if [[ "${CC}" =~ "clang" ]]; then export CODE_COVERAGE=0; fi
if [[ -z "${BUILD_OPENMW}" ]]; then export BUILD_OPENMW=ON; fi
if [[ -z "${BUILD_OPENMW_CS}" ]]; then export BUILD_OPENMW_CS=ON; fi
@ -39,7 +37,6 @@ ${ANALYZE} cmake .. \
-DBUILD_OPENMW_MP=ON \
-DBUILD_BROWSER=ON \
-DBUILD_MASTER=ON \
-DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} \
-DBUILD_UNITTESTS=1 \
-DUSE_SYSTEM_TINYXML=1 \
-DDESIRED_QT_VERSION=5 \

@ -76,53 +76,6 @@ namespace CSMWorld
return false;
}
/* LandMapLodColumn */
LandMapLodColumn::LandMapLodColumn()
: Column<Land>(Columns::ColumnId_LandMapLodIndex, ColumnBase::Display_String, 0)
{
}
QVariant LandMapLodColumn::get(const Record<Land>& record) const
{
const int Size = Land::LAND_GLOBAL_MAP_LOD_SIZE;
const Land& land = record.get();
DataType values(Size, 0);
if (land.mDataTypes & Land::DATA_WNAM)
{
for (int i = 0; i < Size; ++i)
values[i] = land.mWnam[i];
}
QVariant variant;
variant.setValue(values);
return variant;
}
void LandMapLodColumn::set(Record<Land>& record, const QVariant& data)
{
DataType values = data.value<DataType>();
if (values.size() != Land::LAND_GLOBAL_MAP_LOD_SIZE)
throw std::runtime_error("invalid land map LOD data");
Land copy = record.get();
copy.add(Land::DATA_WNAM);
for (int i = 0; i < values.size(); ++i)
{
copy.mWnam[i] = values[i];
}
record.setModified(copy);
}
bool LandMapLodColumn::isEditable() const
{
return true;
}
/* LandNormalsColumn */
LandNormalsColumn::LandNormalsColumn()
: Column<Land>(Columns::ColumnId_LandNormalsIndex, ColumnBase::Display_String, 0)

@ -2461,17 +2461,6 @@ namespace CSMWorld
bool isEditable() const override;
};
struct LandMapLodColumn : public Column<Land>
{
using DataType = QVector<signed char>;
LandMapLodColumn();
QVariant get(const Record<Land>& record) const override;
void set(Record<Land>& record, const QVariant& data) override;
bool isEditable() const override;
};
struct LandNormalsColumn : public Column<Land>
{
using DataType = QVector<signed char>;
@ -2529,8 +2518,7 @@ namespace CSMWorld
}
// This is required to access the type as a QVariant.
Q_DECLARE_METATYPE(CSMWorld::LandMapLodColumn::DataType)
//Q_DECLARE_METATYPE(CSMWorld::LandNormalsColumn::DataType) // Same as LandMapLodColumn::DataType
Q_DECLARE_METATYPE(CSMWorld::LandNormalsColumn::DataType)
Q_DECLARE_METATYPE(CSMWorld::LandHeightsColumn::DataType)
Q_DECLARE_METATYPE(CSMWorld::LandColoursColumn::DataType)
Q_DECLARE_METATYPE(CSMWorld::LandTexturesColumn::DataType)

@ -443,7 +443,6 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
mLand.addColumn (new RecordStateColumn<Land>);
mLand.addColumn (new FixedRecordTypeColumn<Land>(UniversalId::Type_Land));
mLand.addColumn (new LandPluginIndexColumn);
mLand.addColumn (new LandMapLodColumn);
mLand.addColumn (new LandNormalsColumn);
mLand.addColumn (new LandHeightsColumn);
mLand.addColumn (new LandColoursColumn);

@ -705,7 +705,7 @@ void CSVRender::Object::apply (CSMWorld::CommandMacro& commands)
CSMWorld::Columns::ColumnId_PositionXRot+i));
commands.push (new CSMWorld::ModifyCommand (*model,
model->index (recordIndex, column), mPositionOverride.rot[i]));
model->index (recordIndex, column), osg::RadiansToDegrees(mPositionOverride.rot[i])));
}
}

@ -273,7 +273,6 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges()
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex);
int landMapLodColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandMapLodIndex);
int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex);
QUndoStack& undoStack = document.getUndoStack();
@ -287,9 +286,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges()
std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY());
undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId));
const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
const CSMWorld::LandMapLodColumn::DataType landMapLodPointer = landTable.data(landTable.getModelIndex(cellId, landMapLodColumn)).value<CSMWorld::LandMapLodColumn::DataType>();
CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer);
CSMWorld::LandMapLodColumn::DataType mapLodShapeNew(landMapLodPointer);
CSVRender::PagedWorldspaceWidget *paged = dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget());
// Generate land height record
@ -304,26 +301,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges()
}
}
// Generate WNAM record
int sqrtLandGlobalMapLodSize = sqrt(ESM::Land::LAND_GLOBAL_MAP_LOD_SIZE);
for(int i = 0; i < sqrtLandGlobalMapLodSize; ++i)
{
for(int j = 0; j < sqrtLandGlobalMapLodSize; ++j)
{
int col = (static_cast<float>(j) / sqrtLandGlobalMapLodSize) * (ESM::Land::LAND_SIZE - 1);
int row = (static_cast<float>(i) / sqrtLandGlobalMapLodSize) * (ESM::Land::LAND_SIZE - 1);
signed char lodHeight = 0;
float floatLodHeight = 0;
if (landShapeNew[col * ESM::Land::LAND_SIZE + row] > 0) floatLodHeight = landShapeNew[col * ESM::Land::LAND_SIZE + row] / 128;
if (landShapeNew[col * ESM::Land::LAND_SIZE + row] <= 0) floatLodHeight = landShapeNew[col * ESM::Land::LAND_SIZE + row] / 16;
if (floatLodHeight > std::numeric_limits<signed char>::max()) lodHeight = std::numeric_limits<signed char>::max();
else if (floatLodHeight < std::numeric_limits<signed char>::min()) lodHeight = std::numeric_limits<signed char>::min();
else lodHeight = static_cast<signed char>(floatLodHeight);
mapLodShapeNew[j * sqrtLandGlobalMapLodSize + i] = lodHeight;
}
}
pushEditToCommand(landShapeNew, document, landTable, cellId);
pushLodToCommand(mapLodShapeNew, document, landTable, cellId);
}
for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells)
@ -1136,18 +1114,6 @@ void CSVRender::TerrainShapeMode::pushNormalsEditToCommand(const CSMWorld::LandN
undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand));
}
void CSVRender::TerrainShapeMode::pushLodToCommand(const CSMWorld::LandMapLodColumn::DataType& newLandMapLod, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, const std::string& cellId)
{
QVariant changedLod;
changedLod.setValue(newLandMapLod);
QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandMapLodIndex)));
QUndoStack& undoStack = document.getUndoStack();
undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLod));
}
bool CSVRender::TerrainShapeMode::noCell(const std::string& cellId)
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();

@ -148,10 +148,6 @@ namespace CSVRender
void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, const std::string& cellId);
/// Generate new land map LOD
void pushLodToCommand(const CSMWorld::LandMapLodColumn::DataType& newLandMapLod, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, const std::string& cellId);
bool noCell(const std::string& cellId);
bool noLand(const std::string& cellId);

@ -485,22 +485,12 @@ namespace MWMechanics
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))
if (seq.isInCombat() ||
MWBase::Environment::get().getWorld()->isSwimming(actor) ||
(packageId != AiPackage::TypeIdWander && packageId != AiPackage::TypeIdTravel && packageId != -1))
{
stats.setTurningToPlayer(false);
stats.setGreetingTimer(0);
@ -508,6 +498,11 @@ namespace MWMechanics
return;
}
MWWorld::Ptr player = getPlayer();
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
osg::Vec3f dir = playerPos - actorPos;
if (stats.isTurningToPlayer())
{
// Reduce the turning animation glitch by using a *HUGE* value of
@ -525,11 +520,10 @@ namespace MWMechanics
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;
float helloDistance = static_cast<float>(stats.getAiSetting(CreatureStats::AI_Hello).getModified() * iGreetDistanceMultiplier);
int greetingTimer = stats.getGreetingTimer();
GreetingState greetingState = stats.getGreetingState();

@ -197,7 +197,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
zTurn(actor, mPathFinder.getZAngleToNext(position.x(), position.y()));
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
mObstacleCheck.update(actor, duration);
const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front();
mObstacleCheck.update(actor, destination, duration);
// handle obstacles on the way
evadeObstacles(actor);

@ -54,8 +54,9 @@ namespace MWMechanics
stats.setMovementFlag(CreatureStats::Flag_Run, false);
stats.setDrawState(DrawState_Nothing);
// Note: we should cancel internal "return after combat" package, if original location is too far away
if (!isWithinMaxRange(targetPos, actorPos))
return false;
return mHidden;
// Unfortunately, with vanilla assets destination is sometimes blocked by other actor.
// If we got close to target, check for actors nearby. If they are, finish AI package.

@ -3,6 +3,7 @@
#include <components/debug/debuglog.hpp>
#include <components/misc/rng.hpp>
#include <components/esm/aisequence.hpp>
#include <components/detournavigator/navigator.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
@ -52,6 +53,14 @@ namespace MWMechanics
return 1;
return COUNT_BEFORE_RESET;
}
osg::Vec3f getRandomPointAround(const osg::Vec3f& position, const float distance)
{
const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * osg::PI;
osg::Matrixf rotation;
rotation.makeRotate(randomDirection, osg::Vec3f(0.0, 0.0, 1.0));
return position + osg::Vec3f(distance, 0.0, 0.0) * rotation;
}
}
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
@ -136,15 +145,6 @@ namespace MWMechanics
// get or create temporary storage
AiWanderStorage& storage = state.get<AiWanderStorage>();
const MWWorld::CellStore*& currentCell = storage.mCell;
bool cellChange = currentCell && (actor.getCell() != currentCell);
if(!currentCell || cellChange)
{
stopWalking(actor, storage);
currentCell = actor.getCell();
storage.mPopulateAvailableNodes = true;
mStoredInitialActorPosition = false;
}
mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600);
@ -191,14 +191,13 @@ namespace MWMechanics
if (AI_REACTION_TIME <= lastReaction)
{
lastReaction = 0;
return reactionTimeActions(actor, storage, currentCell, cellChange, pos);
return reactionTimeActions(actor, storage, pos);
}
else
return false;
}
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos)
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
{
if (mDistance <= 0)
storage.mCanWanderAlongPathGrid = false;
@ -220,7 +219,7 @@ namespace MWMechanics
// Initialization to discover & store allowed node points for this actor.
if (storage.mPopulateAvailableNodes)
{
getAllowedNodes(actor, currentCell->getCell(), storage);
getAllowedNodes(actor, actor.getCell()->getCell(), storage);
}
if (canActorMoveByZAxis(actor) && mDistance > 0) {
@ -249,10 +248,6 @@ namespace MWMechanics
completeManualWalking(actor, storage);
}
// Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles.
if(mDistance && cellChange)
mDistance = 0;
AiWanderStorage::WanderState& wanderState = storage.mState;
if ((wanderState == AiWanderStorage::Wander_MoveNow) && storage.mCanWanderAlongPathGrid)
{
@ -310,14 +305,24 @@ namespace MWMechanics
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor);
const auto world = MWBase::Environment::get().getWorld();
const auto halfExtents = world->getPathfindingHalfExtents(actor);
const auto navigator = world->getNavigator();
const auto navigatorFlags = getNavigatorFlags(actor);
do {
// Determine a random location within radius of original position
const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance;
const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * osg::PI;
const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection);
const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection);
const float destinationZ = mInitialActorPosition.z();
mDestination = osg::Vec3f(destinationX, destinationY, destinationZ);
if (!isWaterCreature && !isFlyingCreature)
{
// findRandomPointAroundCircle uses wanderDistance as limit for random and not as exact distance
if (const auto destination = navigator->findRandomPointAroundCircle(halfExtents, mInitialActorPosition, wanderDistance, navigatorFlags))
mDestination = *destination;
else
mDestination = getRandomPointAround(mInitialActorPosition, wanderRadius);
}
else
mDestination = getRandomPointAround(mInitialActorPosition, wanderRadius);
// Check if land creature will walk onto water or if water creature will swim onto land
if (!isWaterCreature && destinationIsAtWater(actor, mDestination))
@ -327,15 +332,9 @@ namespace MWMechanics
continue;
if (isWaterCreature || isFlyingCreature)
{
mPathFinder.buildStraightPath(mDestination);
}
else
{
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor);
mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents,
getNavigatorFlags(actor));
}
mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents, navigatorFlags);
if (mPathFinder.isPathConstructed())
{
@ -516,7 +515,7 @@ namespace MWMechanics
unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size());
ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]);
ToWorldCoordinates(dest, storage.mCell->getCell());
ToWorldCoordinates(dest, actor.getCell()->getCell());
// actor position is already in world coordinates
const osg::Vec3f start = actorPos.asVec3();

@ -27,8 +27,6 @@ namespace MWMechanics
{
float mReaction; // update some actions infrequently
const MWWorld::CellStore* mCell; // for detecting cell change
// AiWander states
enum WanderState
{
@ -60,7 +58,6 @@ namespace MWMechanics
AiWanderStorage():
mReaction(0),
mCell(nullptr),
mState(Wander_ChooseAction),
mIsWanderingManually(false),
mCanWanderAlongPathGrid(true),
@ -125,8 +122,7 @@ namespace MWMechanics
void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos);
bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos);
bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
@ -141,7 +137,7 @@ namespace MWMechanics
bool mRepeat;
bool mStoredInitialActorPosition;
osg::Vec3f mInitialActorPosition;
osg::Vec3f mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell
bool mHasDestination;
osg::Vec3f mDestination;

@ -77,89 +77,94 @@ namespace MWMechanics
}
ObstacleCheck::ObstacleCheck()
: mWalkState(State_Norm)
, mStuckDuration(0)
, mEvadeDuration(0)
, mDistSameSpot(-1) // avoid calculating it each time
: mWalkState(WalkState::Initial)
, mStateDuration(0)
, mEvadeDirectionIndex(0)
{
}
void ObstacleCheck::clear()
{
mWalkState = State_Norm;
mStuckDuration = 0;
mEvadeDuration = 0;
mWalkState = WalkState::Initial;
}
bool ObstacleCheck::isEvading() const
{
return mWalkState == State_Evade;
return mWalkState == WalkState::Evade;
}
/*
* input - actor, duration (time since last check)
* output - true if evasive action needs to be taken
*
* Walking state transitions (player greeting check not shown):
* Walking state transitions (player greeting check not shown):
*
* MoveNow <------------------------------------+
* | d|
* | |
* +-> State_Norm <---> State_CheckStuck --> State_Evade
* ^ ^ | f ^ | t ^ | |
* | | | | | | | |
* | +---+ +---+ +---+ | u
* | any < t < u |
* +--------------------------------------------+
* Initial ----> Norm <--------> CheckStuck -------> Evade ---+
* ^ ^ | f ^ | t ^ | |
* | | | | | | | |
* | +-+ +---+ +---+ | u
* | any < t < u |
* +---------------------------------------------+
*
* f = one reaction time
* d = proximity to a closed door
* t = how long before considered stuck
* u = how long to move sideways
*
*/
void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration)
void ObstacleCheck::update(const MWWorld::Ptr& actor, const osg::Vec3f& destination, float duration)
{
const osg::Vec3f pos = actor.getRefData().getPosition().asVec3();
const auto position = actor.getRefData().getPosition().asVec3();
if (mDistSameSpot == -1)
mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor);
if (mWalkState == WalkState::Initial)
{
mWalkState = WalkState::Norm;
mStateDuration = 0;
mPrev = position;
return;
}
const float distSameSpot = mDistSameSpot * duration;
const bool samePosition = (pos - mPrev).length2() < distSameSpot * distSameSpot;
if (mWalkState != WalkState::Evade)
{
const float distSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor) * duration;
const float prevDistance = (destination - mPrev).length();
const float currentDistance = (destination - position).length();
const float movedDistance = prevDistance - currentDistance;
mPrev = pos;
mPrev = position;
if (mWalkState != State_Evade)
{
if(!samePosition)
if (movedDistance >= distSameSpot)
{
mWalkState = WalkState::Norm;
mStateDuration = 0;
return;
}
if (mWalkState == WalkState::Norm)
{
mWalkState = State_Norm;
mStuckDuration = 0;
mEvadeDuration = 0;
mWalkState = WalkState::CheckStuck;
mStateDuration = duration;
return;
}
mWalkState = State_CheckStuck;
mStuckDuration += duration;
// consider stuck only if position unchanges for a period
if(mStuckDuration < DURATION_SAME_SPOT)
return; // still checking, note duration added to timer
else
mStateDuration += duration;
if (mStateDuration < DURATION_SAME_SPOT)
{
mStuckDuration = 0;
mWalkState = State_Evade;
chooseEvasionDirection();
return;
}
mWalkState = WalkState::Evade;
mStateDuration = 0;
chooseEvasionDirection();
return;
}
mEvadeDuration += duration;
if(mEvadeDuration >= DURATION_TO_EVADE)
mStateDuration += duration;
if(mStateDuration >= DURATION_TO_EVADE)
{
// tried to evade, assume all is ok and start again
mWalkState = State_Norm;
mEvadeDuration = 0;
mWalkState = WalkState::Norm;
mStateDuration = 0;
mPrev = position;
}
}

@ -32,30 +32,27 @@ namespace MWMechanics
bool isEvading() const;
// Updates internal state, call each frame for moving actor
void update(const MWWorld::Ptr& actor, float duration);
void update(const MWWorld::Ptr& actor, const osg::Vec3f& destination, float duration);
// change direction to try to fix "stuck" actor
void takeEvasiveAction(MWMechanics::Movement& actorMovement) const;
private:
// for checking if we're stuck
osg::Vec3f mPrev;
// directions to try moving in when get stuck
static const float evadeDirections[NUM_EVADE_DIRECTIONS][2];
enum WalkState
enum class WalkState
{
State_Norm,
State_CheckStuck,
State_Evade
Initial,
Norm,
CheckStuck,
Evade
};
WalkState mWalkState;
float mStuckDuration; // accumulate time here while in same spot
float mEvadeDuration;
float mDistSameSpot; // take account of actor's speed
float mStateDuration;
int mEvadeDirectionIndex;
void chooseEvasionDirection();

@ -314,7 +314,9 @@ namespace MWMechanics
{
mPath.clear();
buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath));
// If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path
if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath)))
mPath.push_back(endPoint);
mConstructed = true;
}
@ -335,7 +337,7 @@ namespace MWMechanics
mConstructed = true;
}
void PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags,
std::back_insert_iterator<std::deque<osg::Vec3f>> out)
{
@ -344,7 +346,7 @@ namespace MWMechanics
const auto world = MWBase::Environment::get().getWorld();
const auto stepSize = getPathStepSize(actor);
const auto navigator = world->getNavigator();
navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out);
return navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out).is_initialized();
}
catch (const DetourNavigator::NavigatorException& exception)
{
@ -352,6 +354,7 @@ namespace MWMechanics
<< "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase()
<< ") from " << startPoint << " to " << endPoint << " with flags ("
<< DetourNavigator::WriteFlags {flags} << ")";
return true;
}
}

@ -201,7 +201,7 @@ namespace MWMechanics
void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const PathgridGraph& pathgridGraph, std::back_insert_iterator<std::deque<osg::Vec3f>> out);
void buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
bool buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags,
std::back_insert_iterator<std::deque<osg::Vec3f>> out);
};

@ -39,6 +39,7 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "interpretercontext.hpp"
#include "ref.hpp"
@ -501,6 +502,14 @@ namespace MWScript
if (!targetPtr.isEmpty() && targetPtr.getCellRef().getRefId() == testedTargetId)
targetsAreEqual = true;
}
else
{
bool turningToPlayer = creatureStats.isTurningToPlayer();
bool greeting = creatureStats.getGreetingState() == MWMechanics::Greet_InProgress;
bool sayActive = MWBase::Environment::get().getSoundManager()->sayActive(actor);
if (turningToPlayer || (greeting && sayActive))
targetsAreEqual = (testedTargetId == "player"); // Currently the player ID is hardcoded
}
runtime.push(int(targetsAreEqual));
}
};

@ -2,11 +2,14 @@
#include <components/detournavigator/navigatorimpl.hpp>
#include <components/detournavigator/exceptions.hpp>
#include <components/misc/rng.hpp>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <boost/optional/optional_io.hpp>
#include <gtest/gtest.h>
#include <iterator>
@ -655,4 +658,32 @@ namespace
osg::Vec3f(215, -215, 1.87718021869659423828125),
})) << mPath;
}
TEST_F(DetourNavigatorNavigatorTest, update_then_find_random_point_around_circle_should_return_position)
{
const std::array<btScalar, 5 * 5> heightfieldData {{
0, 0, 0, 0, 0,
0, -25, -25, -25, -25,
0, -25, -100, -100, -100,
0, -25, -100, -100, -100,
0, -25, -100, -100, -100,
}};
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
shape.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
Misc::Rng::init(42);
const auto result = mNavigator->findRandomPointAroundCircle(mAgentHalfExtents, mStart, 100.0, Flag_walk);
ASSERT_EQ(result, boost::optional<osg::Vec3f>(osg::Vec3f(-209.95985412597656, 129.89768981933594, -0.26253718137741089)));
const auto distance = (*result - mStart).length();
EXPECT_EQ(distance, 85.260780334472656) << distance;
}
}

@ -245,6 +245,8 @@ add_component_dir(detournavigator
recastmeshobject
navmeshtilescache
settings
navigator
findrandompointaroundcircle
)
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui

@ -0,0 +1,45 @@
#include "findrandompointaroundcircle.hpp"
#include "settings.hpp"
#include "findsmoothpath.hpp"
#include <components/misc/rng.hpp>
#include <DetourCommon.h>
#include <DetourNavMesh.h>
#include <DetourNavMeshQuery.h>
namespace DetourNavigator
{
boost::optional<osg::Vec3f> findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents,
const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const Settings& settings)
{
dtNavMeshQuery navMeshQuery;
initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes);
dtQueryFilter queryFilter;
queryFilter.setIncludeFlags(includeFlags);
dtPolyRef startRef = 0;
osg::Vec3f startPolygonPosition;
for (int i = 0; i < 3; ++i)
{
const auto status = navMeshQuery.findNearestPoly(start.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter,
&startRef, startPolygonPosition.ptr());
if (!dtStatusFailed(status) && startRef != 0)
break;
}
if (startRef == 0)
return boost::optional<osg::Vec3f>();
dtPolyRef resultRef = 0;
osg::Vec3f resultPosition;
navMeshQuery.findRandomPointAroundCircle(startRef, start.ptr(), maxRadius, &queryFilter,
&Misc::Rng::rollProbability, &resultRef, resultPosition.ptr());
if (resultRef == 0)
return boost::optional<osg::Vec3f>();
return boost::optional<osg::Vec3f>(resultPosition);
}
}

@ -0,0 +1,20 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDRANDOMPOINTAROUNDCIRCLE_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDRANDOMPOINTAROUNDCIRCLE_H
#include "flags.hpp"
#include <boost/optional.hpp>
#include <osg/Vec3f>
class dtNavMesh;
namespace DetourNavigator
{
struct Settings;
boost::optional<osg::Vec3f> findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents,
const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const Settings& settings);
}
#endif

@ -0,0 +1,20 @@
#include "findrandompointaroundcircle.hpp"
#include "navigator.hpp"
namespace DetourNavigator
{
boost::optional<osg::Vec3f> Navigator::findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents,
const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const
{
const auto navMesh = getNavMesh(agentHalfExtents);
if (!navMesh)
return boost::optional<osg::Vec3f>();
const auto settings = getSettings();
const auto result = DetourNavigator::findRandomPointAroundCircle(navMesh->lockConst()->getImpl(),
toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start),
toNavMeshCoordinates(settings, maxRadius), includeFlags, settings);
if (!result)
return boost::optional<osg::Vec3f>();
return boost::optional<osg::Vec3f>(fromNavMeshCoordinates(settings, *result));
}
}

@ -157,10 +157,9 @@ namespace DetourNavigator
* @param out the beginning of the destination range.
* @return Output iterator to the element in the destination range, one past the last element of found path.
* Equal to out if no path is found.
* @throws InvalidArgument if there is no navmesh for given agentHalfExtents.
*/
template <class OutputIterator>
OutputIterator findPath(const osg::Vec3f& agentHalfExtents, const float stepSize, const osg::Vec3f& start,
boost::optional<OutputIterator> findPath(const osg::Vec3f& agentHalfExtents, const float stepSize, const osg::Vec3f& start,
const osg::Vec3f& end, const Flags includeFlags, OutputIterator out) const
{
static_assert(
@ -172,7 +171,7 @@ namespace DetourNavigator
);
const auto navMesh = getNavMesh(agentHalfExtents);
if (!navMesh)
return out;
return {};
const auto settings = getSettings();
return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents),
toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start),
@ -194,6 +193,17 @@ namespace DetourNavigator
virtual const Settings& getSettings() const = 0;
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
/**
* @brief findRandomPointAroundCircle returns random location on navmesh within the reach of specified location.
* @param agentHalfExtents allows to find navmesh for given actor.
* @param start path from given point.
* @param maxRadius limit maximum distance from start.
* @param includeFlags setup allowed surfaces for actor to walk.
* @return not empty optional with position if point is found and empty optional if point is not found.
*/
boost::optional<osg::Vec3f> findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents,
const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const;
};
}

@ -158,8 +158,24 @@ namespace ESM
}
}
if (mDataTypes & Land::DATA_WNAM) {
esm.writeHNT("WNAM", mWnam, 81);
if (mDataTypes & Land::DATA_WNAM)
{
// Generate WNAM record
signed char wnam[LAND_GLOBAL_MAP_LOD_SIZE];
float max = std::numeric_limits<signed char>::max();
float min = std::numeric_limits<signed char>::min();
float vertMult = static_cast<float>(ESM::Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT;
for (int row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row)
{
for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col)
{
float height = mLandData->mHeights[int(row * vertMult) * ESM::Land::LAND_SIZE + int(col * vertMult)];
height /= height > 0 ? 128.f : 16.f;
height = std::min(max, std::max(min, height));
wnam[row * LAND_GLOBAL_MAP_LOD_SIZE_SQRT + col] = static_cast<signed char>(height);
}
}
esm.writeHNT("WNAM", wnam, 81);
}
if (mLandData)

@ -70,6 +70,8 @@ struct Land
static const int LAND_GLOBAL_MAP_LOD_SIZE = 81;
static const int LAND_GLOBAL_MAP_LOD_SIZE_SQRT = 9;
#pragma pack(push,1)
struct VHGT
{

@ -59,7 +59,7 @@ public:
controller.post(nif);
}
};
typedef Named NiSequenceStreamHelper;
using NiSequenceStreamHelper = Named;
} // Namespace
#endif

@ -10,17 +10,18 @@ namespace Nif
Named::read(nif);
external = nif->getChar() != 0;
if(external)
bool internal = false;
if (external)
filename = nif->getString();
else
{
nif->getChar(); // always 1
internal = nif->getChar();
if (!external && internal)
data.read(nif);
}
pixel = nif->getInt();
mipmap = nif->getInt();
alpha = nif->getInt();
pixel = nif->getUInt();
mipmap = nif->getUInt();
alpha = nif->getUInt();
nif->getChar(); // always 1
}
@ -113,8 +114,4 @@ namespace Nif
mCenter = nif->getVector3();
}
}

@ -46,13 +46,13 @@ public:
3 - Compressed
4 - Bumpmap
5 - Default */
int pixel;
unsigned int pixel;
/* Mipmap format
0 - no
1 - yes
2 - default */
int mipmap;
unsigned int mipmap;
/* Alpha
0 - none
@ -60,7 +60,7 @@ public:
2 - smooth
3 - default (use material alpha, or multiply material with texture if present)
*/
int alpha;
unsigned int alpha;
void read(NIFStream *nif);
void post(NIFFile *nif);

@ -92,6 +92,12 @@ namespace Nif
void NiMaterialColorController::read(NIFStream *nif)
{
Controller::read(nif);
// Two bits that correspond to the controlled material color.
// 00: Ambient
// 01: Diffuse
// 10: Specular
// 11: Emissive
targetColor = (flags >> 4) & 3;
data.read(nif);
}
@ -189,7 +195,8 @@ namespace Nif
{
Controller::read(nif);
data.read(nif);
nif->getChar(); // always 0
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW)
/*bool alwaysActive = */nif->getChar(); // Always 0
}
void NiGeomMorpherController::post(NIFFile *nif)

@ -78,12 +78,13 @@ public:
void read(NIFStream *nif);
void post(NIFFile *nif);
};
typedef NiParticleSystemController NiBSPArrayController;
using NiBSPArrayController = NiParticleSystemController;
class NiMaterialColorController : public Controller
{
public:
NiPosDataPtr data;
unsigned int targetColor;
void read(NIFStream *nif);
void post(NIFFile *nif);

@ -225,7 +225,8 @@ void NiSkinData::read(NIFStream *nif)
trafo.scale = nif->getFloat();
int boneNum = nif->getInt();
nif->getInt(); // -1
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFFile::NIFVersion::VER_GAMEBRYO)
nif->skip(4); // NiSkinPartition link
bones.resize(boneNum);
for (BoneInfo &bi : bones)

@ -143,7 +143,7 @@ void NIFFile::parse(Files::IStreamPtr stream)
ver = nif.getUInt();
// 4.0.0.0 is an older, practically identical version of the format.
// It's not used by Morrowind assets but Morrowind supports it.
if(ver != VER_4_0_0_0 && ver != VER_MW)
if(ver != nif.generateVersion(4,0,0,0) && ver != VER_MW)
fail("Unsupported NIF version: " + printVersion(ver));
// Number of records
size_t recNum = nif.getInt();

@ -75,26 +75,16 @@ class NIFFile final : public File
void operator = (NIFFile const &);
public:
// For generic versions NIFStream::generateVersion() is used instead
enum NIFVersion
{
// Feature-relevant
VER_4_1_0_0 = 0x04010000, // 1-byte booleans (previously 4-byte)
VER_5_0_0_1 = 0x05000001, // Optimized record type listings
VER_5_0_0_6 = 0x05000006, // Record groups
VER_10_0_1_8 = 0x0A000108, // The last version without user version
VER_20_1_0_1 = 0x14010001, // String tables
VER_20_2_0_5 = 0x14020005, // Record sizes
// Game-relevant
VER_4_0_0_0 = 0x04000000, // Freedom Force NIFs, supported by Morrowind
VER_MW = 0x04000002, // 4.0.0.2. Morrowind and Freedom Force NIFs
VER_4_2_1_0 = 0x04020100, // Used in Civ4 and Dark Age of Camelot
VER_CI = 0x04020200, // 4.2.2.0. Main Culpa Innata NIF version, also used in Civ4
VER_ZT2 = 0x0A000100, // 10.0.1.0. Main Zoo Tycoon 2 NIF version, also used in Oblivion and Civ4
VER_OB_OLD = 0x0A000102, // 10.0.1.2. Main older Oblivion NIF version
VER_MW = 0x04000002, // 4.0.0.2. Main Morrowind NIF version.
VER_CI = 0x04020200, // 4.2.2.0. Main Culpa Innata NIF version, also used in Civ4.
VER_ZT2 = 0x0A000100, // 10.0.1.0. Main Zoo Tycoon 2 NIF version, also used in Oblivion and Civ4.
VER_OB_OLD = 0x0A000102, // 10.0.1.2. Main older Oblivion NIF version.
VER_GAMEBRYO = 0x0A010000, // 10.1.0.0. Lots of games use it. The first version that has Gamebryo File Format header.
VER_10_2_0_0 = 0x0A020000, // Lots of games use this version as well.
VER_CIV4 = 0x14000004, // 20.0.0.4. Main Civilization IV NIF version.
VER_OB = 0x14000005, // 20.0.0.5. Main Oblivion NIF version
VER_OB = 0x14000005, // 20.0.0.5. Main Oblivion NIF version.
VER_BGS = 0x14020007 // 20.2.0.7. Main Fallout 3/4/76/New Vegas and Skyrim/SkyrimSE NIF version.
};
enum BethVersion

@ -38,7 +38,7 @@ namespace Nif
}
// Convenience utility functions: get the versions of the currently read file
unsigned int NIFStream::getVersion() { return file->getVersion(); }
unsigned int NIFStream::getUserVersion() { return file->getBethVersion(); }
unsigned int NIFStream::getBethVersion() { return file->getBethVersion(); }
unsigned int NIFStream::getVersion() const { return file->getVersion(); }
unsigned int NIFStream::getUserVersion() const { return file->getBethVersion(); }
unsigned int NIFStream::getBethVersion() const { return file->getBethVersion(); }
}

@ -159,9 +159,15 @@ public:
std::string getString();
unsigned int getVersion();
unsigned int getUserVersion();
unsigned int getBethVersion();
unsigned int getVersion() const;
unsigned int getUserVersion() const;
unsigned int getBethVersion() const;
// Convert human-readable version numbers into a number that can be compared.
static constexpr uint32_t generateVersion(uint8_t major, uint8_t minor, uint8_t patch, uint8_t rev)
{
return (major << 24) + (minor << 16) + (patch << 8) + rev;
}
///Read in a string of the given length
std::string getSizedString(size_t length)

@ -284,7 +284,8 @@ struct NiLODNode : public NiSwitchNode
void read(NIFStream *nif)
{
NiSwitchNode::read(nif);
lodCenter = nif->getVector3();
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFFile::NIFVersion::VER_ZT2)
lodCenter = nif->getVector3();
unsigned int numLodLevels = nif->getUInt();
for (unsigned int i=0; i<numLodLevels; ++i)
{

@ -768,12 +768,7 @@ namespace NifOsg
const Nif::NiMaterialColorController* matctrl = static_cast<const Nif::NiMaterialColorController*>(ctrl.getPtr());
if (matctrl->data.empty())
continue;
// Two bits that correspond to the controlled material color.
// 00: Ambient
// 01: Diffuse
// 10: Specular
// 11: Emissive
MaterialColorController::TargetColor targetColor = static_cast<MaterialColorController::TargetColor>((matctrl->flags >> 4) & 3);
auto targetColor = static_cast<MaterialColorController::TargetColor>(matctrl->targetColor);
osg::ref_ptr<MaterialColorController> osgctrl(new MaterialColorController(matctrl->data.getPtr(), targetColor));
setupController(matctrl, osgctrl, animflags);
composite->addController(osgctrl);

Loading…
Cancel
Save