Merged pull request #1931

pull/541/head
Marc Zinnschlag 6 years ago
commit 9dd0d641bc

@ -233,6 +233,10 @@ namespace MWBase
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0;
virtual void processChangedSettings (const std::set< std::pair<std::string, std::string> >& settings) = 0;
virtual float getActorsProcessingRange() const = 0;
/// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0;

@ -302,6 +302,9 @@ namespace MWBase
virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0;
virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) = 0;
virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0;
virtual bool toggleCollisionMode() = 0;
///< Toggle collision mode for player. If disabled player object should ignore
/// collisions and gravity.

@ -20,6 +20,7 @@
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/inputmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "confirmationdialog.hpp"
@ -437,6 +438,7 @@ namespace MWGui
MWBase::Environment::get().getSoundManager()->processChangedSettings(changed);
MWBase::Environment::get().getWindowManager()->processChangedSettings(changed);
MWBase::Environment::get().getInputManager()->processChangedSettings(changed);
MWBase::Environment::get().getMechanicsManager()->processChangedSettings(changed);
}
void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender)

@ -147,9 +147,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
namespace MWMechanics
{
const float aiProcessingDistance = 7168;
const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance;
class SoulTrap : public MWMechanics::EffectSourceVisitor
{
MWWorld::Ptr mCreature;
@ -364,7 +361,8 @@ namespace MWMechanics
const ESM::Position& actor1Pos = actor1.getRefData().getPosition();
const ESM::Position& actor2Pos = actor2.getRefData().getPosition();
float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2();
if (sqrDist > sqrAiProcessingDistance)
if (sqrDist > mActorsProcessingRange*mActorsProcessingRange)
return;
// No combat for totally static creatures
@ -1130,8 +1128,11 @@ namespace MWMechanics
}
}
Actors::Actors() {
Actors::Actors()
{
mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
updateProcessingRange();
}
Actors::~Actors()
@ -1139,6 +1140,23 @@ namespace MWMechanics
clear();
}
float Actors::getProcessingRange() const
{
return mActorsProcessingRange;
}
void Actors::updateProcessingRange()
{
// We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876)
static const float maxProcessingRange = 7168.f;
static const float minProcessingRange = maxProcessingRange / 2.f;
float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game");
actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange);
actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange);
mActorsProcessingRange = actorsProcessingRange;
}
void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately)
{
removeActor(ptr);
@ -1184,7 +1202,7 @@ namespace MWMechanics
// Otherwise check if any actor in AI processing range sees the target actor
std::vector<MWWorld::Ptr> actors;
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
getObjectsInRange(position, aiProcessingDistance, actors);
getObjectsInRange(position, mActorsProcessingRange, actors);
for(std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); ++it)
{
if (*it == actor)
@ -1242,7 +1260,7 @@ namespace MWMechanics
{
if (iter->first == player) continue;
bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= sqrAiProcessingDistance;
bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= mActorsProcessingRange*mActorsProcessingRange;
if (inProcessingRange)
{
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
@ -1288,7 +1306,8 @@ namespace MWMechanics
if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
// show torches only when there are darkness and no precipitations
bool showTorches = MWBase::Environment::get().getWorld()->useTorches();
MWBase::World* world = MWBase::Environment::get().getWorld();
bool showTorches = world->useTorches();
MWWorld::Ptr player = getPlayer();
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
@ -1301,7 +1320,7 @@ namespace MWMechanics
int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId();
if (attackedByPlayerId != -1)
{
const MWWorld::Ptr playerHitAttemptActor = MWBase::Environment::get().getWorld()->searchPtrViaActorId(attackedByPlayerId);
const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId);
if (!playerHitAttemptActor.isInCell())
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
@ -1314,14 +1333,11 @@ namespace MWMechanics
CharacterController* ctrl = iter->second->getCharacterController();
float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
// AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this
// (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not)
// This distance could be made configurable later, but the setting must be marked with a big warning:
// using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876)
bool inProcessingRange = distSqr <= sqrAiProcessingDistance;
// AI processing is only done within given distance to the player.
bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange;
if (isPlayer)
ctrl->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell());
ctrl->setAttackingOrSpell(world->getPlayer().getAttackingOrSpell());
// If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player.
if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead()
@ -1335,10 +1351,10 @@ namespace MWMechanics
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
{
bool cellChanged = MWBase::Environment::get().getWorld()->hasCellChanged();
bool cellChanged = world->hasCellChanged();
MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports
updateActor(actor, duration);
if (!cellChanged && MWBase::Environment::get().getWorld()->hasCellChanged())
if (!cellChanged && world->hasCellChanged())
{
return; // for now abort update of the old cell when cell changes by teleportation magic effect
// a better solution might be to apply cell changes at the end of the frame
@ -1363,7 +1379,7 @@ namespace MWMechanics
MWWorld::Ptr headTrackTarget;
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson();
bool firstPersonPlayer = isPlayer && world->isFirstPerson();
// 1. Unconsious actor can not track target
// 2. Actors in combat and pursue mode do not bother to headtrack
@ -1423,27 +1439,25 @@ namespace MWMechanics
CharacterController* playerCharacter = nullptr;
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{
const float animationDistance = aiProcessingDistance + 400; // Slightly larger than AI distance so there is time to switch back to the idle animation.
const float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length();
bool isPlayer = iter->first == player;
bool inAnimationRange = isPlayer || (animationDistance == 0 || distSqr <= animationDistance*animationDistance);
bool inRange = isPlayer || dist <= mActorsProcessingRange;
int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower)
if (isPlayer)
activeFlag = 2;
int active = inAnimationRange ? activeFlag : 0;
bool canFly = iter->first.getClass().canFly(iter->first);
if (canFly)
{
// Keep animating flying creatures so they don't just hover in-air
inAnimationRange = true;
active = std::max(1, active);
}
int active = inRange ? activeFlag : 0;
CharacterController* ctrl = iter->second->getCharacterController();
ctrl->setActive(active);
if (!inAnimationRange)
if (!inRange)
{
iter->first.getRefData().getBaseNode()->setNodeMask(0);
world->setActorCollisionMode(iter->first, false);
continue;
}
else if (!isPlayer)
iter->first.getRefData().getBaseNode()->setNodeMask(1<<3);
if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed())
ctrl->skipAnim();
@ -1455,11 +1469,28 @@ namespace MWMechanics
playerCharacter = ctrl;
continue;
}
world->setActorCollisionMode(iter->first, true);
ctrl->update(duration);
// Fade away actors on large distance (>90% of actor's processing distance)
float visibilityRatio = 1.0;
float fadeStartDistance = mActorsProcessingRange*0.9f;
float fadeEndDistance = mActorsProcessingRange;
float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance);
if (fadeRatio > 0)
visibilityRatio -= std::max(0.f, fadeRatio);
visibilityRatio = std::min(1.f, visibilityRatio);
ctrl->setVisibility(visibilityRatio);
}
if (playerCharacter)
{
playerCharacter->update(duration);
playerCharacter->setVisibility(1.f);
}
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{
@ -1671,7 +1702,7 @@ namespace MWMechanics
restoreDynamicStats(iter->first, sleep);
if ((!iter->first.getRefData().getBaseNode()) ||
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > sqrAiProcessingDistance)
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange)
continue;
adjustMagicEffects (iter->first);
@ -1915,7 +1946,7 @@ namespace MWMechanics
std::list<MWWorld::Ptr> list;
std::vector<MWWorld::Ptr> neighbors;
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
getObjectsInRange(position, aiProcessingDistance, neighbors);
getObjectsInRange(position, mActorsProcessingRange, neighbors);
for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
{
if (*neighbor == actor)
@ -1936,7 +1967,7 @@ namespace MWMechanics
std::list<MWWorld::Ptr> list;
std::vector<MWWorld::Ptr> neighbors;
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
getObjectsInRange(position, aiProcessingDistance, neighbors);
getObjectsInRange(position, mActorsProcessingRange, neighbors);
std::set<MWWorld::Ptr> followers;
getActorsFollowing(actor, followers);

@ -65,6 +65,9 @@ namespace MWMechanics
/// paused we may want to do it manually (after equipping permanent enchantment)
void updateMagicEffects (const MWWorld::Ptr& ptr);
void updateProcessingRange();
float getProcessingRange() const;
void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false);
///< Register an actor for stats management
///
@ -168,6 +171,7 @@ namespace MWMechanics
private:
PtrActorMap mActors;
float mTimerDisposeSummonsCorpses;
float mActorsProcessingRange;
};
}

@ -103,7 +103,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
/// Stops the actor when it gets too close to a unloaded cell
//... At current time, this test is unnecessary. AI shuts down when actor is more than 7168
//... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" setting value
//... units from player, and exterior cells are 8192 units long and wide.
//... But AI processing distance may increase in the future.
if (isNearInactiveCell(pos))
@ -354,7 +354,7 @@ bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos)
// currently assumes 3 x 3 grid for exterior cells, with player at center cell.
// ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells
// While AI Process distance is 7168, AI shuts down actors before they reach edges of 3 x 3 grid.
// AI shuts down actors before they reach edges of 3 x 3 grid.
const float distanceFromEdge = 200.0;
float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge;
float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge;

@ -3,8 +3,9 @@
#include <components/esm/aisequence.hpp>
#include <components/esm/loadcell.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
@ -21,7 +22,8 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2)
// Maximum travel distance for vanilla compatibility.
// Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well.
// We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways.
return (pos1 - pos2).length2() <= 7168*7168;
bool aiDistance = MWBase::Environment::get().getMechanicsManager()->getActorsProcessingRange();
return (pos1 - pos2).length2() <= aiDistance*aiDistance;
}
}

@ -30,6 +30,7 @@
#include "../mwrender/animation.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
@ -1849,7 +1850,7 @@ void CharacterController::updateAnimQueue()
mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1);
}
void CharacterController::update(float duration)
void CharacterController::update(float duration, bool animationOnly)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
const MWWorld::Class &cls = mPtr.getClass();
@ -2235,10 +2236,10 @@ void CharacterController::update(float duration)
world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true);
}
if (!mMovementAnimationControlled)
if (!animationOnly && !mMovementAnimationControlled)
world->queueMovement(mPtr, vec);
}
else
else if (!animationOnly)
// We must always queue movement, even if there is none, to apply gravity.
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
@ -2261,7 +2262,8 @@ void CharacterController::update(float duration)
playDeath(1.f, mDeathState);
}
// We must always queue movement, even if there is none, to apply gravity.
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
if (!animationOnly)
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
}
bool isPersist = isPersistentAnimPlaying();
@ -2295,7 +2297,7 @@ void CharacterController::update(float duration)
moved.z() = 1.0;
// Update movement
if(mMovementAnimationControlled && mPtr.getClass().isActor())
if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor())
world->queueMovement(mPtr, moved);
mSkipAnim = false;
@ -2544,20 +2546,6 @@ void CharacterController::updateMagicEffects()
{
if (!mPtr.getClass().isActor())
return;
float alpha = 1.f;
if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555).
{
if (mPtr == getPlayer())
alpha = 0.4f;
else
alpha = 0.f;
}
float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude();
if (chameleon)
{
alpha *= std::max(0.2f, (100.f - chameleon)/100.f);
}
mAnimation->setAlpha(alpha);
bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f;
mAnimation->setVampire(vampire);
@ -2566,6 +2554,32 @@ void CharacterController::updateMagicEffects()
mAnimation->setLightEffect(light);
}
void CharacterController::setVisibility(float visibility)
{
// We should take actor's invisibility in account
if (mPtr.getClass().isActor())
{
float alpha = 1.f;
if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555).
{
if (mPtr == getPlayer())
alpha = 0.4f;
else
alpha = 0.f;
}
float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude();
if (chameleon)
{
alpha *= std::max(0.2f, (100.f - chameleon)/100.f);
}
visibility = std::min(visibility, alpha);
}
// TODO: implement a dithering shader rather than just change object transparency.
mAnimation->setAlpha(visibility);
}
void CharacterController::setAttackTypeBasedOnMovement()
{
float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition;

@ -257,7 +257,7 @@ public:
void updatePtr(const MWWorld::Ptr &ptr);
void update(float duration);
void update(float duration, bool animationOnly=false);
void persistAnimationState();
void unpersistAnimationState();
@ -292,6 +292,7 @@ public:
bool isTurning() const;
bool isAttackingOrSpell() const;
void setVisibility(float visibility);
void setAttackingOrSpell(bool attackingOrSpell);
void castSpell(const std::string spellId, bool manualSpell=false);
void setAIAttackType(const std::string& attackType);

@ -431,6 +431,25 @@ namespace MWMechanics
mObjects.update(duration, paused);
}
void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector &changed)
{
for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it)
{
if (it->first == "Game" && it->second == "actors processing range")
{
mActors.updateProcessingRange();
// Update mechanics for new processing range immediately
update(0.f, false);
}
}
}
float MechanicsManager::getActorsProcessingRange() const
{
return mActors.getProcessingRange();
}
bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer)
{
return mActors.isActorDetected(actor, observer);

@ -1,6 +1,8 @@
#ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
#define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
#include <components/settings/settings.hpp>
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/ptr.hpp"
@ -206,6 +208,10 @@ namespace MWMechanics
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false);
void processChangedSettings(const Settings::CategorySettingVector& settings) override;
virtual float getActorsProcessingRange() const;
/// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer);

@ -1366,6 +1366,33 @@ namespace MWPhysics
return false;
}
void PhysicsSystem::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled)
{
ActorMap::iterator found = mActors.find(ptr);
if (found != mActors.end())
{
bool cmode = found->second->getCollisionMode();
if (cmode == enabled)
return;
cmode = enabled;
found->second->enableCollisionMode(cmode);
found->second->enableCollisionBody(cmode);
}
}
bool PhysicsSystem::isActorCollisionEnabled(const MWWorld::Ptr& ptr)
{
ActorMap::iterator found = mActors.find(ptr);
if (found != mActors.end())
{
bool cmode = found->second->getCollisionMode();
return cmode;
}
return false;
}
void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement)
{
PtrVelocityList::iterator iter = mMovementQueue.begin();

@ -86,6 +86,8 @@ namespace MWPhysics
void removeHeightField (int x, int y);
bool toggleCollisionMode();
bool isActorCollisionEnabled(const MWWorld::Ptr& ptr);
void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled);
void stepSimulation(float dt);
void debugDraw();

@ -1739,21 +1739,31 @@ namespace MWRender
if (alpha != 1.f)
{
osg::StateSet* stateset (new osg::StateSet);
// If we have an existing material for alpha transparency, just override alpha level
osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet();
osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
if (material)
{
material->setAlpha(osg::Material::FRONT_AND_BACK, alpha);
}
else
{
osg::StateSet* stateset (new osg::StateSet);
osg::BlendFunc* blendfunc (new osg::BlendFunc);
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
osg::BlendFunc* blendfunc (new osg::BlendFunc);
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
// FIXME: overriding diffuse/ambient/emissive colors
osg::Material* material (new osg::Material);
material->setColorMode(osg::Material::OFF);
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha));
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
// FIXME: overriding diffuse/ambient/emissive colors
material = new osg::Material;
material->setColorMode(osg::Material::OFF);
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha));
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
mObjectRoot->setStateSet(stateset);
mObjectRoot->setStateSet(stateset);
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
}
}
else
{

@ -1583,6 +1583,16 @@ namespace MWWorld
}
}
void World::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled)
{
mPhysics->setActorCollisionMode(ptr, enabled);
}
bool World::isActorCollisionEnabled(const MWWorld::Ptr& ptr)
{
return mPhysics->isActorCollisionEnabled(ptr);
}
bool World::toggleCollisionMode()
{
if (mPhysics->toggleCollisionMode())
@ -3560,7 +3570,13 @@ namespace MWWorld
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
origin, feetToGameUnits(static_cast<float>(effectIt->mArea)), objects);
for (std::vector<MWWorld::Ptr>::iterator affected = objects.begin(); affected != objects.end(); ++affected)
{
// Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range.
if (affected->getClass().isActor() && !isActorCollisionEnabled(*affected))
continue;
toApply[*affected].push_back(*effectIt);
}
}
// Now apply the appropriate effects to each actor in range

@ -407,6 +407,9 @@ namespace MWWorld
bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override;
void setActorCollisionMode(const Ptr& ptr, bool enabled) override;
bool isActorCollisionEnabled(const Ptr& ptr) override;
bool toggleCollisionMode() override;
///< Toggle collision mode for player. If disabled player object should ignore
/// collisions and gravity.

@ -97,8 +97,21 @@ and values above 500 will result in the player inflicting no damage.
This setting can be controlled in game with the Difficulty slider in the Prefs panel of the Options menu.
actors processing range
-----------------------
:Type: integer
:Range: 3584 to 7168
:Default: 7168
This setting allows to specify a distance from player in game units, in which OpenMW updates actor's state.
Actor state update includes AI, animations, and physics processing.
Actors near that border start softly fade out instead of just appearing/disapperaing.
This setting can be controlled in game with the "Actors processing range slider" in the Prefs panel of the Options menu.
classic reflected absorb spells behavior
-----------------------------------------
----------------------------------------
:Type: boolean
:Range: True/False

@ -73,7 +73,32 @@
<Property key="TextAlign" value="Right"/>
</Widget>
</Widget>
<Widget type="HBox" skin="" position="4 200 260 24">
<Widget type="Widget" skin="" position="4 184 352 54" align="Left Top HStretch">
<Widget type="TextBox" skin="NormalText" position="0 0 352 16" align="Left Top" name="ActorProcessingText">
<Property key="Caption" value="Actors processing range"/>
</Widget>
<Widget type="MWScrollBar" skin="MW_HScroll" position="0 20 352 14" align="Left Top HStretch">
<Property key="Range" value="3584"/>
<Property key="Page" value="300"/>
<UserString key="SettingType" value="Slider"/>
<UserString key="SettingCategory" value="Game"/>
<UserString key="SettingName" value="actors processing range"/>
<UserString key="SettingValueType" value="Float"/>
<UserString key="SettingMin" value="3584"/>
<UserString key="SettingMax" value="7168"/>
<UserString key="SettingLabelWidget" value="ActorProcessingText"/>
<UserString key="SettingLabelCaption" value="Actors processing range"/>
</Widget>
<Widget type="TextBox" skin="SandText" position="0 38 352 16" align="Left Top">
<Property key="Caption" value="#{sLow}"/>
<Property key="TextAlign" value="Left"/>
</Widget>
<Widget type="TextBox" skin="SandText" position="0 38 352 16" align="Right Top">
<Property key="Caption" value="#{sHigh}"/>
<Property key="TextAlign" value="Right"/>
</Widget>
</Widget>
<Widget type="HBox" skin="" position="4 250 260 24">
<Widget type="AutoSizedButton" skin="MW_Button" position="0 0 24 24" align="Left Top">
<UserString key="SettingCategory" value="Saves"/>
<UserString key="SettingName" value="autosave"/>
@ -83,7 +108,7 @@
<Property key="Caption" value="#{sQuick_Save}"/>
</Widget>
</Widget>
<Widget type="HBox" skin="" position="4 230 260 24">
<Widget type="HBox" skin="" position="4 280 260 24">
<Widget type="AutoSizedButton" skin="MW_Button" position="0 0 24 24" align="Left Top">
<UserString key="SettingCategory" value="Game"/>
<UserString key="SettingName" value="best attack"/>
@ -93,7 +118,7 @@
<Property key="Caption" value="#{sBestAttack}"/>
</Widget>
</Widget>
<Widget type="HBox" skin="" position="4 260 260 24">
<Widget type="HBox" skin="" position="4 310 260 24">
<Widget type="AutoSizedButton" skin="MW_Button" position="0 0 24 24" align="Left Top">
<UserString key="SettingCategory" value="GUI"/>
<UserString key="SettingName" value="subtitles"/>
@ -103,7 +128,7 @@
<Property key="Caption" value="#{sSubtitles}"/>
</Widget>
</Widget>
<Widget type="HBox" skin="" position="4 290 260 24">
<Widget type="HBox" skin="" position="4 340 260 24">
<Widget type="AutoSizedButton" skin="MW_Button" position="0 0 24 24" align="Left Top">
<UserString key="SettingCategory" value="HUD"/>
<UserString key="SettingName" value="crosshair"/>

@ -200,6 +200,9 @@ best attack = false
# Difficulty. Expressed as damage dealt and received. (e.g. -100 to 100).
difficulty = 0
# The maximum range of actor AI, animations and physics updates.
actors processing range = 7168
# Make reflected Absorb spells have no practical effect, like in Morrowind.
classic reflected absorb spells behavior = true

Loading…
Cancel
Save