mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-21 12:53:51 +00:00
Merge branch 'master' into 'rotation'
# Conflicts: # CHANGELOG.md
This commit is contained in:
commit
ad1021721f
17 changed files with 413 additions and 87 deletions
|
@ -49,6 +49,7 @@
|
||||||
Bug #5557: Diagonal movement is noticeably slower with analogue stick
|
Bug #5557: Diagonal movement is noticeably slower with analogue stick
|
||||||
Feature #390: 3rd person look "over the shoulder"
|
Feature #390: 3rd person look "over the shoulder"
|
||||||
Feature #2386: Distant Statics in the form of Object Paging
|
Feature #2386: Distant Statics in the form of Object Paging
|
||||||
|
Feature #4894: Consider actors as obstacles for pathfinding
|
||||||
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
|
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
|
||||||
Feature #5362: Show the soul gems' trapped soul in count dialog
|
Feature #5362: Show the soul gems' trapped soul in count dialog
|
||||||
Feature #5445: Handle NiLines
|
Feature #5445: Handle NiLines
|
||||||
|
@ -59,6 +60,7 @@
|
||||||
Feature #5525: Search fields tweaks (utf-8)
|
Feature #5525: Search fields tweaks (utf-8)
|
||||||
Feature #5545: Option to allow stealing from an unconscious NPC during combat
|
Feature #5545: Option to allow stealing from an unconscious NPC during combat
|
||||||
Feature #5579: MCP SetAngle enhancement
|
Feature #5579: MCP SetAngle enhancement
|
||||||
|
Feature #5610: Actors movement should be smoother
|
||||||
Task #5480: Drop Qt4 support
|
Task #5480: Drop Qt4 support
|
||||||
Task #5520: Improve cell name autocompleter implementation
|
Task #5520: Improve cell name autocompleter implementation
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,7 @@ bool Launcher::AdvancedPage::loadSettings()
|
||||||
loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
|
loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
|
||||||
loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
|
loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
|
||||||
loadSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
|
loadSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
|
||||||
|
loadSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game");
|
||||||
int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game");
|
int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game");
|
||||||
if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2)
|
if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2)
|
||||||
unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex);
|
unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex);
|
||||||
|
@ -112,6 +113,7 @@ bool Launcher::AdvancedPage::loadSettings()
|
||||||
loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
||||||
}
|
}
|
||||||
loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
|
loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
|
||||||
|
loadSettingBool(smoothMovementCheckBox, "smooth movement", "Game");
|
||||||
|
|
||||||
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
|
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
|
||||||
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
|
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
|
||||||
|
@ -200,6 +202,7 @@ void Launcher::AdvancedPage::saveSettings()
|
||||||
saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
|
saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
|
||||||
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
|
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
|
||||||
saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
|
saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
|
||||||
|
saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game");
|
||||||
int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex();
|
int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex();
|
||||||
if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game"))
|
if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game"))
|
||||||
mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
|
mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
|
||||||
|
@ -220,6 +223,7 @@ void Launcher::AdvancedPage::saveSettings()
|
||||||
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
|
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
|
||||||
saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
||||||
saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
|
saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
|
||||||
|
saveSettingBool(smoothMovementCheckBox, "smooth movement", "Game");
|
||||||
|
|
||||||
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
|
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
|
||||||
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
|
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/misc/rng.hpp>
|
#include <components/misc/rng.hpp>
|
||||||
|
#include <components/misc/mathutil.hpp>
|
||||||
#include <components/settings/settings.hpp>
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
#include "aicombataction.hpp"
|
#include "aicombataction.hpp"
|
||||||
#include "aifollow.hpp"
|
#include "aifollow.hpp"
|
||||||
#include "aipursue.hpp"
|
#include "aipursue.hpp"
|
||||||
|
#include "aiwander.hpp"
|
||||||
#include "actor.hpp"
|
#include "actor.hpp"
|
||||||
#include "summoning.hpp"
|
#include "summoning.hpp"
|
||||||
#include "combat.hpp"
|
#include "combat.hpp"
|
||||||
|
@ -424,7 +426,7 @@ namespace MWMechanics
|
||||||
const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3());
|
const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3());
|
||||||
float sqrDist = (actor1Pos - actor2Pos).length2();
|
float sqrDist = (actor1Pos - actor2Pos).length2();
|
||||||
|
|
||||||
if (sqrDist > maxDistance*maxDistance)
|
if (sqrDist > std::min(maxDistance * maxDistance, sqrHeadTrackDistance))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// stop tracking when target is behind the actor
|
// stop tracking when target is behind the actor
|
||||||
|
@ -432,10 +434,7 @@ namespace MWMechanics
|
||||||
osg::Vec3f targetDirection(actor2Pos - actor1Pos);
|
osg::Vec3f targetDirection(actor2Pos - actor1Pos);
|
||||||
actorDirection.z() = 0;
|
actorDirection.z() = 0;
|
||||||
targetDirection.z() = 0;
|
targetDirection.z() = 0;
|
||||||
actorDirection.normalize();
|
if (actorDirection * targetDirection > 0
|
||||||
targetDirection.normalize();
|
|
||||||
if (std::acos(actorDirection * targetDirection) < osg::DegreesToRadians(90.f)
|
|
||||||
&& sqrDist <= sqrHeadTrackDistance
|
|
||||||
&& MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function
|
&& MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function
|
||||||
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor))
|
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor))
|
||||||
{
|
{
|
||||||
|
@ -473,6 +472,9 @@ namespace MWMechanics
|
||||||
|
|
||||||
void Actors::updateMovementSpeed(const MWWorld::Ptr& actor)
|
void Actors::updateMovementSpeed(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
|
if (mSmoothMovement)
|
||||||
|
return;
|
||||||
|
|
||||||
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
|
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
|
||||||
MWMechanics::AiSequence& seq = stats.getAiSequence();
|
MWMechanics::AiSequence& seq = stats.getAiSequence();
|
||||||
|
|
||||||
|
@ -481,9 +483,10 @@ namespace MWMechanics
|
||||||
osg::Vec3f targetPos = seq.getActivePackage().getDestination();
|
osg::Vec3f targetPos = seq.getActivePackage().getDestination();
|
||||||
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||||
float distance = (targetPos - actorPos).length();
|
float distance = (targetPos - actorPos).length();
|
||||||
|
|
||||||
if (distance < DECELERATE_DISTANCE)
|
if (distance < DECELERATE_DISTANCE)
|
||||||
{
|
{
|
||||||
float speedCoef = std::max(0.7f, 0.1f * (distance/64.f + 2.f));
|
float speedCoef = std::max(0.7f, 0.2f + 0.8f * distance / DECELERATE_DISTANCE);
|
||||||
auto& movement = actor.getClass().getMovementSettings(actor);
|
auto& movement = actor.getClass().getMovementSettings(actor);
|
||||||
movement.mPosition[0] *= speedCoef;
|
movement.mPosition[0] *= speedCoef;
|
||||||
movement.mPosition[1] *= speedCoef;
|
movement.mPosition[1] *= speedCoef;
|
||||||
|
@ -587,7 +590,10 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (!actorState.isTurningToPlayer())
|
if (!actorState.isTurningToPlayer())
|
||||||
{
|
{
|
||||||
actorState.setAngleToPlayer(std::atan2(dir.x(), dir.y()));
|
float angle = std::atan2(dir.x(), dir.y());
|
||||||
|
actorState.setAngleToPlayer(angle);
|
||||||
|
float deltaAngle = Misc::normalizeAngle(angle - actor.getRefData().getPosition().rot[2]);
|
||||||
|
if (!mSmoothMovement || std::abs(deltaAngle) > osg::DegreesToRadians(60.f))
|
||||||
actorState.setTurningToPlayer(true);
|
actorState.setTurningToPlayer(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1460,7 +1466,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Actors::Actors()
|
Actors::Actors() : mSmoothMovement(Settings::Manager::getBool("smooth movement", "Game"))
|
||||||
{
|
{
|
||||||
mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
|
mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
|
||||||
|
|
||||||
|
@ -1659,6 +1665,131 @@ namespace MWMechanics
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Actors::predictAndAvoidCollisions()
|
||||||
|
{
|
||||||
|
const float minGap = 10.f;
|
||||||
|
const float maxDistToCheck = 100.f;
|
||||||
|
const float maxTimeToCheck = 1.f;
|
||||||
|
static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game");
|
||||||
|
|
||||||
|
MWWorld::Ptr player = getPlayer();
|
||||||
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
|
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||||
|
{
|
||||||
|
const MWWorld::Ptr& ptr = iter->first;
|
||||||
|
if (ptr == player)
|
||||||
|
continue; // Don't interfere with player controls.
|
||||||
|
|
||||||
|
Movement& movement = ptr.getClass().getMovementSettings(ptr);
|
||||||
|
osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]);
|
||||||
|
bool isMoving = origMovement.length2() > 0.01;
|
||||||
|
|
||||||
|
// Moving NPCs always should avoid collisions.
|
||||||
|
// Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either
|
||||||
|
// follow player or have a AIWander package with non-empty wander area.
|
||||||
|
bool shouldAvoidCollision = isMoving;
|
||||||
|
bool shouldTurnToApproachingActor = !isMoving;
|
||||||
|
MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets).
|
||||||
|
for (const auto& package : ptr.getClass().getCreatureStats(ptr).getAiSequence())
|
||||||
|
{
|
||||||
|
if (package->getTypeId() == AiPackageTypeId::Follow)
|
||||||
|
shouldAvoidCollision = true;
|
||||||
|
else if (package->getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle)
|
||||||
|
{
|
||||||
|
if (!dynamic_cast<const AiWander*>(package.get())->isStationary())
|
||||||
|
shouldAvoidCollision = true;
|
||||||
|
}
|
||||||
|
else if (package->getTypeId() == AiPackageTypeId::Combat || package->getTypeId() == AiPackageTypeId::Pursue)
|
||||||
|
{
|
||||||
|
currentTarget = package->getTarget();
|
||||||
|
shouldAvoidCollision = isMoving;
|
||||||
|
shouldTurnToApproachingActor = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!shouldAvoidCollision)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float maxSpeed = ptr.getClass().getMaxSpeed(ptr);
|
||||||
|
osg::Vec2f baseSpeed = origMovement * maxSpeed;
|
||||||
|
osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3();
|
||||||
|
float baseRotZ = ptr.getRefData().getPosition().rot[2];
|
||||||
|
osg::Vec3f halfExtents = world->getHalfExtents(ptr);
|
||||||
|
|
||||||
|
float timeToCollision = maxTimeToCheck;
|
||||||
|
osg::Vec2f movementCorrection(0, 0);
|
||||||
|
float angleToApproachingActor = 0;
|
||||||
|
|
||||||
|
// Iterate through all other actors and predict collisions.
|
||||||
|
for(PtrActorMap::iterator otherIter(mActors.begin()); otherIter != mActors.end(); ++otherIter)
|
||||||
|
{
|
||||||
|
const MWWorld::Ptr& otherPtr = otherIter->first;
|
||||||
|
if (otherPtr == ptr || otherPtr == currentTarget)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr);
|
||||||
|
osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos;
|
||||||
|
osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ);
|
||||||
|
|
||||||
|
// Ignore actors which are not close enough or come from behind.
|
||||||
|
if (deltaPos.length2() > maxDistToCheck * maxDistToCheck || relPos.y() < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Don't check for a collision if vertical distance is greater then the actor's height.
|
||||||
|
if (deltaPos.z() > halfExtents.z() * 2 || deltaPos.z() < -otherHalfExtents.z() * 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
osg::Vec3f speed = otherPtr.getClass().getMovementSettings(otherPtr).asVec3() *
|
||||||
|
otherPtr.getClass().getMaxSpeed(otherPtr);
|
||||||
|
float rotZ = otherPtr.getRefData().getPosition().rot[2];
|
||||||
|
osg::Vec2f relSpeed = Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed;
|
||||||
|
|
||||||
|
float collisionDist = minGap + world->getHalfExtents(ptr).x() + world->getHalfExtents(otherPtr).x();
|
||||||
|
collisionDist = std::min(collisionDist, relPos.length());
|
||||||
|
|
||||||
|
// Find the earliest `t` when |relPos + relSpeed * t| == collisionDist.
|
||||||
|
float vr = relPos.x() * relSpeed.x() + relPos.y() * relSpeed.y();
|
||||||
|
float v2 = relSpeed.length2();
|
||||||
|
float Dh = vr * vr - v2 * (relPos.length2() - collisionDist * collisionDist);
|
||||||
|
if (Dh <= 0 || v2 == 0)
|
||||||
|
continue; // No solution; distance is always >= collisionDist.
|
||||||
|
float t = (-vr - std::sqrt(Dh)) / v2;
|
||||||
|
|
||||||
|
if (t < 0 || t > timeToCollision)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Check visibility and awareness last as it's expensive.
|
||||||
|
if (!MWBase::Environment::get().getWorld()->getLOS(otherPtr, ptr))
|
||||||
|
continue;
|
||||||
|
if (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(otherPtr, ptr))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
timeToCollision = t;
|
||||||
|
angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y());
|
||||||
|
osg::Vec2f posAtT = relPos + relSpeed * t;
|
||||||
|
float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * maxSpeed);
|
||||||
|
movementCorrection = posAtT * coef;
|
||||||
|
// Step to the side rather than backward. Otherwise player will be able to push the NPC far away from it's original location.
|
||||||
|
movementCorrection.y() = std::max(0.f, movementCorrection.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeToCollision < maxTimeToCheck)
|
||||||
|
{
|
||||||
|
// Try to evade the nearest collision.
|
||||||
|
osg::Vec2f newMovement = origMovement + movementCorrection;
|
||||||
|
if (isMoving)
|
||||||
|
{ // Keep the original speed.
|
||||||
|
newMovement.normalize();
|
||||||
|
newMovement *= origMovement.length();
|
||||||
|
}
|
||||||
|
movement.mPosition[0] = newMovement.x();
|
||||||
|
movement.mPosition[1] = newMovement.y();
|
||||||
|
if (shouldTurnToApproachingActor)
|
||||||
|
zTurn(ptr, angleToApproachingActor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Actors::update (float duration, bool paused)
|
void Actors::update (float duration, bool paused)
|
||||||
{
|
{
|
||||||
if(!paused)
|
if(!paused)
|
||||||
|
@ -1769,14 +1900,12 @@ namespace MWMechanics
|
||||||
|
|
||||||
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||||
bool firstPersonPlayer = isPlayer && world->isFirstPerson();
|
bool firstPersonPlayer = isPlayer && world->isFirstPerson();
|
||||||
|
bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue);
|
||||||
|
|
||||||
// 1. Unconsious actor can not track target
|
// 1. Unconsious actor can not track target
|
||||||
// 2. Actors in combat and pursue mode do not bother to headtrack
|
// 2. Actors in combat and pursue mode do not bother to headtrack
|
||||||
// 3. Player character does not use headtracking in the 1st-person view
|
// 3. Player character does not use headtracking in the 1st-person view
|
||||||
if (!stats.getKnockedDown() &&
|
if (!stats.getKnockedDown() && !firstPersonPlayer && !inCombatOrPursue)
|
||||||
!stats.getAiSequence().isInCombat() &&
|
|
||||||
!stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue) &&
|
|
||||||
!firstPersonPlayer)
|
|
||||||
{
|
{
|
||||||
for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
|
for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
|
||||||
{
|
{
|
||||||
|
@ -1786,6 +1915,17 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!stats.getKnockedDown() && !isPlayer && inCombatOrPursue)
|
||||||
|
{
|
||||||
|
// Actors in combat and pursue mode always look at their target.
|
||||||
|
for (const auto& package : stats.getAiSequence())
|
||||||
|
{
|
||||||
|
headTrackTarget = package->getTarget();
|
||||||
|
if (!headTrackTarget.isEmpty())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctrl->setHeadTrackTarget(headTrackTarget);
|
ctrl->setHeadTrackTarget(headTrackTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1824,6 +1964,10 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const bool avoidCollisions = Settings::Manager::getBool("NPCs avoid collisions", "Game");
|
||||||
|
if (avoidCollisions)
|
||||||
|
predictAndAvoidCollisions();
|
||||||
|
|
||||||
timerUpdateAITargets += duration;
|
timerUpdateAITargets += duration;
|
||||||
timerUpdateHeadTrack += duration;
|
timerUpdateHeadTrack += duration;
|
||||||
timerUpdateEquippedLight += duration;
|
timerUpdateEquippedLight += duration;
|
||||||
|
|
|
@ -63,6 +63,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
void purgeSpellEffects (int casterActorId);
|
void purgeSpellEffects (int casterActorId);
|
||||||
|
|
||||||
|
void predictAndAvoidCollisions();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Actors();
|
Actors();
|
||||||
|
@ -209,6 +211,7 @@ namespace MWMechanics
|
||||||
float mTimerDisposeSummonsCorpses;
|
float mTimerDisposeSummonsCorpses;
|
||||||
float mActorsProcessingRange;
|
float mActorsProcessingRange;
|
||||||
|
|
||||||
|
bool mSmoothMovement;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterContro
|
||||||
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||||
|
|
||||||
actorClass.getMovementSettings(actor).mPosition[1] = 1;
|
actorClass.getMovementSettings(actor).mPosition[1] = 1;
|
||||||
smoothTurn(actor, -180, 0);
|
smoothTurn(actor, -osg::PI / 2, 0);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include <components/esm/aisequence.hpp>
|
#include <components/esm/aisequence.hpp>
|
||||||
|
|
||||||
|
#include <components/misc/mathutil.hpp>
|
||||||
|
|
||||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
|
|
||||||
#include "../mwphysics/collisiontype.hpp"
|
#include "../mwphysics/collisiontype.hpp"
|
||||||
|
@ -240,10 +242,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (storage.mReadyToAttack)
|
if (storage.mReadyToAttack)
|
||||||
{
|
{
|
||||||
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
|
||||||
// start new attack
|
|
||||||
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
|
||||||
|
|
||||||
if (isRangedCombat)
|
if (isRangedCombat)
|
||||||
{
|
{
|
||||||
// rotate actor taking into account target movement direction and projectile speed
|
// rotate actor taking into account target movement direction and projectile speed
|
||||||
|
@ -259,6 +257,10 @@ namespace MWMechanics
|
||||||
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
||||||
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
||||||
|
// start new attack
|
||||||
|
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -372,9 +374,13 @@ namespace MWMechanics
|
||||||
void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage)
|
void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage)
|
||||||
{
|
{
|
||||||
// apply combat movement
|
// apply combat movement
|
||||||
|
float deltaAngle = storage.mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2];
|
||||||
|
osg::Vec2f movement = Misc::rotateVec2f(
|
||||||
|
osg::Vec2f(storage.mMovement.mPosition[0], storage.mMovement.mPosition[1]), -deltaAngle);
|
||||||
|
|
||||||
MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor);
|
MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor);
|
||||||
actorMovementSettings.mPosition[0] = storage.mMovement.mPosition[0];
|
actorMovementSettings.mPosition[0] = movement.x();
|
||||||
actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1];
|
actorMovementSettings.mPosition[1] = movement.y();
|
||||||
actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2];
|
actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2];
|
||||||
|
|
||||||
rotateActorOnAxis(actor, 2, actorMovementSettings, storage);
|
rotateActorOnAxis(actor, 2, actorMovementSettings, storage);
|
||||||
|
@ -385,26 +391,11 @@ namespace MWMechanics
|
||||||
MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage)
|
MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage)
|
||||||
{
|
{
|
||||||
actorMovementSettings.mRotation[axis] = 0;
|
actorMovementSettings.mRotation[axis] = 0;
|
||||||
float& targetAngleRadians = storage.mMovement.mRotation[axis];
|
|
||||||
if (targetAngleRadians != 0)
|
|
||||||
{
|
|
||||||
// Some attack animations contain small amount of movement.
|
|
||||||
// Since we use cone shapes for melee, we can use a threshold to avoid jittering
|
|
||||||
std::shared_ptr<Action>& currentAction = storage.mCurrentAction;
|
|
||||||
bool isRangedCombat = false;
|
bool isRangedCombat = false;
|
||||||
currentAction->getCombatRange(isRangedCombat);
|
storage.mCurrentAction->getCombatRange(isRangedCombat);
|
||||||
// Check if the actor now facing desired direction, no need to turn any more
|
float eps = isRangedCombat ? osg::DegreesToRadians(0.5) : osg::DegreesToRadians(3.f);
|
||||||
if (isRangedCombat)
|
float targetAngleRadians = storage.mMovement.mRotation[axis];
|
||||||
{
|
smoothTurn(actor, targetAngleRadians, axis, eps);
|
||||||
if (smoothTurn(actor, targetAngleRadians, axis))
|
|
||||||
targetAngleRadians = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (smoothTurn(actor, targetAngleRadians, axis, osg::DegreesToRadians(3.f)))
|
|
||||||
targetAngleRadians = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MWWorld::Ptr AiCombat::getTarget() const
|
MWWorld::Ptr AiCombat::getTarget() const
|
||||||
|
@ -489,12 +480,19 @@ namespace MWMechanics
|
||||||
// Note: do not use for ranged combat yet since in couple with back up behaviour can move actor out of cliff
|
// Note: do not use for ranged combat yet since in couple with back up behaviour can move actor out of cliff
|
||||||
else if (actor.getClass().isBipedal(actor))
|
else if (actor.getClass().isBipedal(actor))
|
||||||
{
|
{
|
||||||
// apply sideway movement (kind of dodging) with some probability
|
float moveDuration = 0;
|
||||||
// if actor is within range of target's weapon
|
float angleToTarget = Misc::normalizeAngle(mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2]);
|
||||||
if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
|
// Apply a big side step if enemy tries to get around and come from behind.
|
||||||
|
// Otherwise apply a random side step (kind of dodging) with some probability
|
||||||
|
// if actor is within range of target's weapon.
|
||||||
|
if (std::abs(angleToTarget) > osg::PI / 4)
|
||||||
|
moveDuration = 0.2;
|
||||||
|
else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
|
||||||
|
moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
|
||||||
|
if (moveDuration > 0)
|
||||||
{
|
{
|
||||||
mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right
|
mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right
|
||||||
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
|
mTimerCombatMove = moveDuration;
|
||||||
mCombatMove = true;
|
mCombatMove = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <components/esm/loadmgef.hpp>
|
#include <components/esm/loadmgef.hpp>
|
||||||
#include <components/detournavigator/navigator.hpp>
|
#include <components/detournavigator/navigator.hpp>
|
||||||
#include <components/misc/coordinateconverter.hpp>
|
#include <components/misc/coordinateconverter.hpp>
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
@ -87,6 +88,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
//... But AI processing distance may increase in the future.
|
//... But AI processing distance may increase in the future.
|
||||||
if (isNearInactiveCell(position))
|
if (isNearInactiveCell(position))
|
||||||
{
|
{
|
||||||
|
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||||
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
||||||
return false;
|
return false;
|
||||||
|
@ -169,12 +171,34 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn to next path point by X,Z axes
|
// turn to next path point by X,Z axes
|
||||||
zTurn(actor, mPathFinder.getZAngleToNext(position.x(), position.y()));
|
float zAngleToNext = mPathFinder.getZAngleToNext(position.x(), position.y());
|
||||||
|
zTurn(actor, zAngleToNext);
|
||||||
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
|
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
|
||||||
|
|
||||||
const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front();
|
const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front();
|
||||||
mObstacleCheck.update(actor, destination, duration);
|
mObstacleCheck.update(actor, destination, duration);
|
||||||
|
|
||||||
|
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||||
|
if (smoothMovement)
|
||||||
|
{
|
||||||
|
const float smoothTurnReservedDist = 150;
|
||||||
|
auto& movement = actor.getClass().getMovementSettings(actor);
|
||||||
|
float distToNextSqr = osg::Vec2f(destination.x() - position.x(), destination.y() - position.y()).length2();
|
||||||
|
float diffAngle = zAngleToNext - actor.getRefData().getPosition().rot[2];
|
||||||
|
if (std::cos(diffAngle) < -0.1)
|
||||||
|
movement.mPosition[0] = movement.mPosition[1] = 0;
|
||||||
|
else if (distToNextSqr > smoothTurnReservedDist * smoothTurnReservedDist)
|
||||||
|
{ // Go forward (and slowly turn towards the next path point)
|
||||||
|
movement.mPosition[0] = 0;
|
||||||
|
movement.mPosition[1] = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // Next path point is near, so use diagonal movement to follow the path precisely.
|
||||||
|
movement.mPosition[0] = std::sin(diffAngle);
|
||||||
|
movement.mPosition[1] = std::max(std::cos(diffAngle), 0.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handle obstacles on the way
|
// handle obstacles on the way
|
||||||
evadeObstacles(actor);
|
evadeObstacles(actor);
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
static const int COUNT_BEFORE_RESET = 10;
|
static const int COUNT_BEFORE_RESET = 10;
|
||||||
static const float DOOR_CHECK_INTERVAL = 1.5f;
|
static const float IDLE_POSITION_CHECK_INTERVAL = 1.5f;
|
||||||
|
|
||||||
// to prevent overcrowding
|
// to prevent overcrowding
|
||||||
static const int DESTINATION_TOLERANCE = 64;
|
static const int DESTINATION_TOLERANCE = 64;
|
||||||
|
@ -96,6 +96,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
void stopMovement(const MWWorld::Ptr& actor)
|
void stopMovement(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
|
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,15 +425,14 @@ namespace MWMechanics
|
||||||
|
|
||||||
void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
// Check if an idle actor is too close to a door - if so start walking
|
// Check if an idle actor is too far from all allowed nodes or too close to a door - if so start walking.
|
||||||
storage.mDoorCheckDuration += duration;
|
storage.mCheckIdlePositionTimer += duration;
|
||||||
|
|
||||||
if (storage.mDoorCheckDuration >= DOOR_CHECK_INTERVAL)
|
if (storage.mCheckIdlePositionTimer >= IDLE_POSITION_CHECK_INTERVAL && !isStationary())
|
||||||
{
|
{
|
||||||
storage.mDoorCheckDuration = 0; // restart timer
|
storage.mCheckIdlePositionTimer = 0; // restart timer
|
||||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f;
|
||||||
if (mDistance && // actor is not intended to be stationary
|
if (proximityToDoor(actor, distance) || !isNearAllowedNode(actor, storage, distance))
|
||||||
proximityToDoor(actor, distance*1.6f))
|
|
||||||
{
|
{
|
||||||
storage.setState(AiWanderStorage::Wander_MoveNow);
|
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||||
storage.mTrimCurrentNode = false; // just in case
|
storage.mTrimCurrentNode = false; // just in case
|
||||||
|
@ -451,6 +451,20 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AiWander::isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
|
||||||
|
{
|
||||||
|
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||||
|
auto cell = actor.getCell()->getCell();
|
||||||
|
for (const ESM::Pathgrid::Point& node : storage.mAllowedNodes)
|
||||||
|
{
|
||||||
|
osg::Vec3f point(node.mX, node.mY, node.mZ);
|
||||||
|
Misc::CoordinateConverter(cell).toWorld(point);
|
||||||
|
if ((actorPos - point).length2() < distance * distance)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
// Is there no destination or are we there yet?
|
// Is there no destination or are we there yet?
|
||||||
|
@ -468,6 +482,9 @@ namespace MWMechanics
|
||||||
|
|
||||||
void AiWander::onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
void AiWander::onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
|
// Wait while fully stop before starting idle animation (important if "smooth movement" is enabled).
|
||||||
|
if (actor.getClass().getCurrentSpeed(actor) > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
unsigned short idleAnimation = getRandomIdle();
|
unsigned short idleAnimation = getRandomIdle();
|
||||||
storage.mIdleAnimation = idleAnimation;
|
storage.mIdleAnimation = idleAnimation;
|
||||||
|
|
|
@ -53,7 +53,7 @@ namespace MWMechanics
|
||||||
ESM::Pathgrid::Point mCurrentNode;
|
ESM::Pathgrid::Point mCurrentNode;
|
||||||
bool mTrimCurrentNode;
|
bool mTrimCurrentNode;
|
||||||
|
|
||||||
float mDoorCheckDuration;
|
float mCheckIdlePositionTimer;
|
||||||
int mStuckCount;
|
int mStuckCount;
|
||||||
|
|
||||||
AiWanderStorage():
|
AiWanderStorage():
|
||||||
|
@ -66,7 +66,7 @@ namespace MWMechanics
|
||||||
mPopulateAvailableNodes(true),
|
mPopulateAvailableNodes(true),
|
||||||
mAllowedNodes(),
|
mAllowedNodes(),
|
||||||
mTrimCurrentNode(false),
|
mTrimCurrentNode(false),
|
||||||
mDoorCheckDuration(0), // TODO: maybe no longer needed
|
mCheckIdlePositionTimer(0),
|
||||||
mStuckCount(0)
|
mStuckCount(0)
|
||||||
{};
|
{};
|
||||||
|
|
||||||
|
@ -117,6 +117,8 @@ namespace MWMechanics
|
||||||
return mDestination;
|
return mDestination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isStationary() const { return mDistance == 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void stopWalking(const MWWorld::Ptr& actor);
|
void stopWalking(const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
|
@ -137,6 +139,7 @@ namespace MWMechanics
|
||||||
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
|
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
|
||||||
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
|
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
|
||||||
void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage);
|
void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage);
|
||||||
|
bool isNearAllowedNode(const MWWorld::Ptr &actor, const AiWanderStorage& storage, float distance) const;
|
||||||
|
|
||||||
const int mDistance; // how far the actor can wander from the spawn point
|
const int mDistance; // how far the actor can wander from the spawn point
|
||||||
const int mDuration;
|
const int mDuration;
|
||||||
|
|
|
@ -1963,6 +1963,50 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
if (isPlayer && !isrunning && !sneak && !flying && movementSettings.mSpeedFactor <= 0.5f)
|
if (isPlayer && !isrunning && !sneak && !flying && movementSettings.mSpeedFactor <= 0.5f)
|
||||||
movementSettings.mSpeedFactor *= 2.f;
|
movementSettings.mSpeedFactor *= 2.f;
|
||||||
|
|
||||||
|
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||||
|
if (smoothMovement && !isFirstPersonPlayer)
|
||||||
|
{
|
||||||
|
float angle = mPtr.getRefData().getPosition().rot[2];
|
||||||
|
osg::Vec2f targetSpeed = Misc::rotateVec2f(osg::Vec2f(vec.x(), vec.y()), -angle) * movementSettings.mSpeedFactor;
|
||||||
|
osg::Vec2f delta = targetSpeed - mSmoothedSpeed;
|
||||||
|
float speedDelta = movementSettings.mSpeedFactor - mSmoothedSpeed.length();
|
||||||
|
float deltaLen = delta.length();
|
||||||
|
|
||||||
|
float maxDelta;
|
||||||
|
if (std::abs(speedDelta) < deltaLen / 2)
|
||||||
|
// Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point).
|
||||||
|
maxDelta = duration * (isPlayer ? 3.f : 6.f);
|
||||||
|
else if (isPlayer && speedDelta < -deltaLen / 2)
|
||||||
|
// As soon as controls are released, mwinput switches player from running to walking.
|
||||||
|
// So stopping should be instant for player, otherwise it causes a small twitch.
|
||||||
|
maxDelta = 1;
|
||||||
|
else // In all other cases speeding up and stopping are smooth.
|
||||||
|
maxDelta = duration * 3.f;
|
||||||
|
|
||||||
|
if (deltaLen > maxDelta)
|
||||||
|
delta *= maxDelta / deltaLen;
|
||||||
|
mSmoothedSpeed += delta;
|
||||||
|
|
||||||
|
osg::Vec2f newSpeed = Misc::rotateVec2f(mSmoothedSpeed, angle);
|
||||||
|
movementSettings.mSpeedFactor = newSpeed.normalize();
|
||||||
|
vec.x() = newSpeed.x();
|
||||||
|
vec.y() = newSpeed.y();
|
||||||
|
|
||||||
|
const float eps = 0.001f;
|
||||||
|
if (movementSettings.mSpeedFactor < eps)
|
||||||
|
{
|
||||||
|
movementSettings.mSpeedFactor = 0;
|
||||||
|
vec.x() = 0;
|
||||||
|
vec.y() = 1;
|
||||||
|
}
|
||||||
|
else if ((vec.y() < 0) != mIsMovingBackward)
|
||||||
|
{
|
||||||
|
if (targetSpeed.length() < eps || (movementSettings.mPosition[1] < 0) == mIsMovingBackward)
|
||||||
|
vec.y() = mIsMovingBackward ? -eps : eps;
|
||||||
|
}
|
||||||
|
vec.normalize();
|
||||||
|
}
|
||||||
|
|
||||||
float effectiveRotation = rot.z();
|
float effectiveRotation = rot.z();
|
||||||
bool canMove = cls.getMaxSpeed(mPtr) > 0;
|
bool canMove = cls.getMaxSpeed(mPtr) > 0;
|
||||||
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
|
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
|
||||||
|
@ -1994,6 +2038,8 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2);
|
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2);
|
||||||
else
|
else
|
||||||
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4);
|
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4);
|
||||||
|
if (smoothMovement && !isPlayer && !inwater)
|
||||||
|
mAnimation->setUpperBodyYawRadians(mAnimation->getUpperBodyYawRadians() + mAnimation->getHeadYaw() / 2);
|
||||||
|
|
||||||
speed = cls.getCurrentSpeed(mPtr);
|
speed = cls.getCurrentSpeed(mPtr);
|
||||||
vec.x() *= speed;
|
vec.x() *= speed;
|
||||||
|
@ -2185,13 +2231,11 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
: (sneak ? CharState_SneakBack
|
: (sneak ? CharState_SneakBack
|
||||||
: (isrunning ? CharState_RunBack : CharState_WalkBack)));
|
: (isrunning ? CharState_RunBack : CharState_WalkBack)));
|
||||||
}
|
}
|
||||||
else if (effectiveRotation != 0.0f)
|
else
|
||||||
{
|
{
|
||||||
// Do not play turning animation for player if rotation speed is very slow.
|
// Do not play turning animation for player if rotation speed is very slow.
|
||||||
// Actual threshold should take framerate in account.
|
// Actual threshold should take framerate in account.
|
||||||
float rotationThreshold = 0.f;
|
float rotationThreshold = (isPlayer ? 0.015f : 0.001f) * 60 * duration;
|
||||||
if (isPlayer)
|
|
||||||
rotationThreshold = 0.015 * 60 * duration;
|
|
||||||
|
|
||||||
// It seems only bipedal actors use turning animations.
|
// It seems only bipedal actors use turning animations.
|
||||||
// Also do not use turning animations in the first-person view and when sneaking.
|
// Also do not use turning animations in the first-person view and when sneaking.
|
||||||
|
@ -2695,10 +2739,9 @@ void CharacterController::setVisibility(float visibility)
|
||||||
void CharacterController::setAttackTypeBasedOnMovement()
|
void CharacterController::setAttackTypeBasedOnMovement()
|
||||||
{
|
{
|
||||||
float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition;
|
float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition;
|
||||||
|
if (std::abs(move[1]) > std::abs(move[0]) + 0.2f) // forward-backward
|
||||||
if (move[1] && !move[0]) // forward-backward
|
|
||||||
mAttackType = "thrust";
|
mAttackType = "thrust";
|
||||||
else if (move[0] && !move[1]) //sideway
|
else if (std::abs(move[0]) > std::abs(move[1]) + 0.2f) // sideway
|
||||||
mAttackType = "slash";
|
mAttackType = "slash";
|
||||||
else
|
else
|
||||||
mAttackType = "chop";
|
mAttackType = "chop";
|
||||||
|
@ -2893,19 +2936,21 @@ void CharacterController::updateHeadTracking(float duration)
|
||||||
return;
|
return;
|
||||||
const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0);
|
const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0);
|
||||||
|
|
||||||
zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y());
|
zAngleRadians = std::atan2(actorDirection.x(), actorDirection.y()) - std::atan2(direction.x(), direction.y());
|
||||||
xAngleRadians = -std::asin(direction.z());
|
xAngleRadians = std::asin(direction.z());
|
||||||
|
}
|
||||||
|
|
||||||
const double xLimit = osg::DegreesToRadians(40.0);
|
const double xLimit = osg::DegreesToRadians(40.0);
|
||||||
const double zLimit = osg::DegreesToRadians(30.0);
|
const double zLimit = osg::DegreesToRadians(30.0);
|
||||||
zAngleRadians = osg::clampBetween(Misc::normalizeAngle(zAngleRadians), -xLimit, xLimit);
|
double zLimitOffset = mAnimation->getUpperBodyYawRadians();
|
||||||
xAngleRadians = osg::clampBetween(Misc::normalizeAngle(xAngleRadians), -zLimit, zLimit);
|
xAngleRadians = osg::clampBetween(Misc::normalizeAngle(xAngleRadians), -xLimit, xLimit);
|
||||||
}
|
zAngleRadians = osg::clampBetween(Misc::normalizeAngle(zAngleRadians),
|
||||||
|
-zLimit + zLimitOffset, zLimit + zLimitOffset);
|
||||||
|
|
||||||
float factor = duration*5;
|
float factor = duration*5;
|
||||||
factor = std::min(factor, 1.f);
|
factor = std::min(factor, 1.f);
|
||||||
xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngleRadians);
|
xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * xAngleRadians;
|
||||||
zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngleRadians);
|
zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * zAngleRadians;
|
||||||
|
|
||||||
mAnimation->setHeadPitch(xAngleRadians);
|
mAnimation->setHeadPitch(xAngleRadians);
|
||||||
mAnimation->setHeadYaw(zAngleRadians);
|
mAnimation->setHeadYaw(zAngleRadians);
|
||||||
|
|
|
@ -196,6 +196,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
||||||
float mTimeUntilWake;
|
float mTimeUntilWake;
|
||||||
|
|
||||||
bool mIsMovingBackward;
|
bool mIsMovingBackward;
|
||||||
|
osg::Vec2f mSmoothedSpeed;
|
||||||
|
|
||||||
void setAttackTypeBasedOnMovement();
|
void setAttackTypeBasedOnMovement();
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,24 @@ namespace
|
||||||
const auto halfExtents = world->getHalfExtents(actor);
|
const auto halfExtents = world->getHalfExtents(actor);
|
||||||
return 2.0 * halfExtents.z();
|
return 2.0 * halfExtents.z();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if turn in `p2` is less than 10 degrees and all the 3 points are almost on one line.
|
||||||
|
bool isAlmostStraight(const osg::Vec3f& p1, const osg::Vec3f& p2, const osg::Vec3f& p3, float pointTolerance) {
|
||||||
|
osg::Vec3f v1 = p1 - p2;
|
||||||
|
osg::Vec3f v3 = p3 - p2;
|
||||||
|
v1.z() = v3.z() = 0;
|
||||||
|
float dotProduct = v1.x() * v3.x() + v1.y() * v3.y();
|
||||||
|
float crossProduct = v1.x() * v3.y() - v1.y() * v3.x();
|
||||||
|
|
||||||
|
// Check that the angle between v1 and v3 is less or equal than 10 degrees.
|
||||||
|
static const float cos170 = std::cos(osg::PI / 180 * 170);
|
||||||
|
bool checkAngle = dotProduct <= cos170 * v1.length() * v3.length();
|
||||||
|
|
||||||
|
// Check that distance from p2 to the line (p1, p3) is less or equal than `pointTolerance`.
|
||||||
|
bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length() * 2;
|
||||||
|
|
||||||
|
return checkAngle && checkDist;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
|
@ -286,6 +304,11 @@ namespace MWMechanics
|
||||||
while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance)
|
while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance)
|
||||||
mPath.pop_front();
|
mPath.pop_front();
|
||||||
|
|
||||||
|
while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance))
|
||||||
|
mPath.erase(mPath.begin() + 1);
|
||||||
|
if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance))
|
||||||
|
mPath.pop_front();
|
||||||
|
|
||||||
if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance)
|
if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance)
|
||||||
mPath.pop_front();
|
mPath.pop_front();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#include "steering.hpp"
|
#include "steering.hpp"
|
||||||
|
|
||||||
|
#include <components/misc/mathutil.hpp>
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
|
@ -12,19 +15,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians)
|
bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians)
|
||||||
{
|
{
|
||||||
float currentAngle (actor.getRefData().getPosition().rot[axis]);
|
MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor);
|
||||||
float diff (targetAngleRadians - currentAngle);
|
float diff = Misc::normalizeAngle(targetAngleRadians - actor.getRefData().getPosition().rot[axis]);
|
||||||
if (std::abs(diff) >= osg::DegreesToRadians(180.f))
|
|
||||||
{
|
|
||||||
if (diff >= 0)
|
|
||||||
{
|
|
||||||
diff = diff - osg::DegreesToRadians(360.f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
diff = osg::DegreesToRadians(360.f) + diff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
float absDiff = std::abs(diff);
|
float absDiff = std::abs(diff);
|
||||||
|
|
||||||
// The turning animation actually moves you slightly, so the angle will be wrong again.
|
// The turning animation actually moves you slightly, so the angle will be wrong again.
|
||||||
|
@ -33,10 +25,14 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
float limit = getAngularVelocity(actor.getClass().getMaxSpeed(actor)) * MWBase::Environment::get().getFrameDuration();
|
float limit = getAngularVelocity(actor.getClass().getMaxSpeed(actor)) * MWBase::Environment::get().getFrameDuration();
|
||||||
|
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||||
|
if (smoothMovement)
|
||||||
|
limit *= std::min(absDiff / osg::PI + 0.1, 0.5);
|
||||||
|
|
||||||
if (absDiff > limit)
|
if (absDiff > limit)
|
||||||
diff = osg::sign(diff) * limit;
|
diff = osg::sign(diff) * limit;
|
||||||
|
|
||||||
actor.getClass().getMovementSettings(actor).mRotation[axis] = diff;
|
movement.mRotation[axis] = diff;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1507,6 +1507,8 @@ namespace NifOsg
|
||||||
|
|
||||||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||||
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
uvSet = tex.uvSet;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -331,8 +331,43 @@ If enabled then the character turns lower body to the direction of movement. Upp
|
||||||
|
|
||||||
This setting can be controlled in Advanced tab of the launcher.
|
This setting can be controlled in Advanced tab of the launcher.
|
||||||
|
|
||||||
|
smooth movement
|
||||||
|
---------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Makes NPCs and player movement more smooth.
|
||||||
|
|
||||||
|
Recommended to use with "turn to movement direction" enabled.
|
||||||
|
|
||||||
|
This setting can be controlled in Advanced tab of the launcher.
|
||||||
|
|
||||||
|
NPCs avoid collisions
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
If enabled NPCs apply evasion maneuver to avoid collisions with others.
|
||||||
|
|
||||||
|
This setting can be controlled in Advanced tab of the launcher.
|
||||||
|
|
||||||
|
NPCs give way
|
||||||
|
-------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: True
|
||||||
|
|
||||||
|
Standing NPCs give way to moving ones. Works only if 'NPCs avoid collisions' is enabled.
|
||||||
|
|
||||||
|
This setting can only be configured by editing the settings configuration file.
|
||||||
|
|
||||||
swim upward correction
|
swim upward correction
|
||||||
----------------
|
----------------------
|
||||||
|
|
||||||
:Type: boolean
|
:Type: boolean
|
||||||
:Range: True/False
|
:Range: True/False
|
||||||
|
|
|
@ -325,6 +325,15 @@ uncapped damage fatigue = false
|
||||||
# Turn lower body to movement direction. 'true' makes diagonal movement more realistic.
|
# Turn lower body to movement direction. 'true' makes diagonal movement more realistic.
|
||||||
turn to movement direction = false
|
turn to movement direction = false
|
||||||
|
|
||||||
|
# Makes all movements of NPCs and player more smooth.
|
||||||
|
smooth movement = false
|
||||||
|
|
||||||
|
# All actors avoid collisions with other actors.
|
||||||
|
NPCs avoid collisions = false
|
||||||
|
|
||||||
|
# Give way to moving actors when idle. Requires 'NPCs avoid collisions' to be enabled.
|
||||||
|
NPCs give way = true
|
||||||
|
|
||||||
# Makes player swim a bit upward from the line of sight.
|
# Makes player swim a bit upward from the line of sight.
|
||||||
swim upward correction = false
|
swim upward correction = false
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,16 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QCheckBox" name="avoidCollisionsCheckBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>NPCs avoid collisions</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="4" column="0">
|
||||||
<widget class="QCheckBox" name="enableNavigatorCheckBox">
|
<widget class="QCheckBox" name="enableNavigatorCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
|
@ -233,6 +243,16 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QCheckBox" name="smoothMovementCheckBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Smooth movement</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QCheckBox" name="distantLandCheckBox">
|
<widget class="QCheckBox" name="distantLandCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
|
@ -283,7 +303,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<item row="6" column="0">
|
||||||
<widget class="QCheckBox" name="animSourcesCheckBox">
|
<widget class="QCheckBox" name="animSourcesCheckBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html></string>
|
<string><html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html></string>
|
||||||
|
|
Loading…
Reference in a new issue