mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-07-12 23:51:42 +00:00
Add OpenMW commits up to 4 Feb 2020
# Conflicts: # .travis.yml # CI/before_script.linux.sh
This commit is contained in:
commit
f0f76516d8
39 changed files with 328 additions and 277 deletions
13
.travis.yml
13
.travis.yml
|
@ -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
|
||||
|
|
3
CI/before_script.linux.sh
Executable file → Normal file
3
CI/before_script.linux.sh
Executable file → Normal file
|
@ -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);
|
||||
|
||||
const float distSameSpot = mDistSameSpot * duration;
|
||||
const bool samePosition = (pos - mPrev).length2() < distSameSpot * distSameSpot;
|
||||
|
||||
mPrev = pos;
|
||||
|
||||
if (mWalkState != State_Evade)
|
||||
if (mWalkState == WalkState::Initial)
|
||||
{
|
||||
if(!samePosition)
|
||||
mWalkState = WalkState::Norm;
|
||||
mStateDuration = 0;
|
||||
mPrev = position;
|
||||
return;
|
||||
}
|
||||
|
||||
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 = position;
|
||||
|
||||
if (movedDistance >= distSameSpot)
|
||||
{
|
||||
mWalkState = State_Norm;
|
||||
mStuckDuration = 0;
|
||||
mEvadeDuration = 0;
|
||||
mWalkState = WalkState::Norm;
|
||||
mStateDuration = 0;
|
||||
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
|
||||
if (mWalkState == WalkState::Norm)
|
||||
{
|
||||
mStuckDuration = 0;
|
||||
mWalkState = State_Evade;
|
||||
chooseEvasionDirection();
|
||||
mWalkState = WalkState::CheckStuck;
|
||||
mStateDuration = duration;
|
||||
return;
|
||||
}
|
||||
|
||||
mStateDuration += duration;
|
||||
if (mStateDuration < DURATION_SAME_SPOT)
|
||||
{
|
||||
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
|
||||
|
|
45
components/detournavigator/findrandompointaroundcircle.cpp
Normal file
45
components/detournavigator/findrandompointaroundcircle.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
20
components/detournavigator/findrandompointaroundcircle.hpp
Normal file
20
components/detournavigator/findrandompointaroundcircle.hpp
Normal file
|
@ -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
|
20
components/detournavigator/navigator.cpp
Normal file
20
components/detournavigator/navigator.cpp
Normal file
|
@ -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
|
||||
data.read(nif);
|
||||
}
|
||||
internal = nif->getChar();
|
||||
|
||||
pixel = nif->getInt();
|
||||
mipmap = nif->getInt();
|
||||
alpha = nif->getInt();
|
||||
if (!external && internal)
|
||||
data.read(nif);
|
||||
|
||||
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…
Reference in a new issue