1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-04-01 12:36:42 +00:00

Merge remote-tracking branch 'refs/remotes/upstream/master'

This commit is contained in:
AnyOldName3 2018-10-24 20:13:13 +01:00
commit 1e2bf9c447
25 changed files with 274 additions and 120 deletions

View file

@ -130,15 +130,20 @@
Bug #4633: Sneaking stance affects speed even if the actor is not able to crouch Bug #4633: Sneaking stance affects speed even if the actor is not able to crouch
Bug #4641: GetPCJumping is handled incorrectly Bug #4641: GetPCJumping is handled incorrectly
Bug #4644: %Name should be available for all actors, not just for NPCs Bug #4644: %Name should be available for all actors, not just for NPCs
Bug #4646: Weapon force-equipment messes up ongoing attack animations
Bug #4648: Hud thinks that throwing weapons have condition Bug #4648: Hud thinks that throwing weapons have condition
Bug #4649: Levelup fully restores health Bug #4649: Levelup fully restores health
Bug #4653: Length of non-ASCII strings is handled incorrectly in ESM reader Bug #4653: Length of non-ASCII strings is handled incorrectly in ESM reader
Bug #4654: Editor: UpdateVisitor does not initialize skeletons for animated objects Bug #4654: Editor: UpdateVisitor does not initialize skeletons for animated objects
Bug #4656: Combat AI: back up behaviour is incorrect
Bug #4668: Editor: Light source color is displayed as an integer Bug #4668: Editor: Light source color is displayed as an integer
Bug #4669: ToggleCollision should trace the player down after collision being enabled Bug #4669: ToggleCollision should trace the player down after collision being enabled
Bug #4671: knownEffect functions should use modified Alchemy skill Bug #4671: knownEffect functions should use modified Alchemy skill
Bug #4672: Pitch factor is handled incorrectly for crossbow animations Bug #4672: Pitch factor is handled incorrectly for crossbow animations
Bug #4674: Journal can be opened when settings window is open Bug #4674: Journal can be opened when settings window is open
Bug #4677: Crash in ESM reader when NPC record has DNAM record without DODT one
Bug #4678: Crash in ESP parser when SCVR has no variable names
Bug #4685: Missing sound causes an exception inside Say command
Feature #912: Editor: Add missing icons to UniversalId tables Feature #912: Editor: Add missing icons to UniversalId tables
Feature #1221: Editor: Creature/NPC rendering Feature #1221: Editor: Creature/NPC rendering
Feature #1617: Editor: Enchantment effect record verifier Feature #1617: Editor: Enchantment effect record verifier
@ -153,6 +158,7 @@
Feature #4012: Editor: Write a log file if OpenCS crashes Feature #4012: Editor: Write a log file if OpenCS crashes
Feature #4222: 360° screenshots Feature #4222: 360° screenshots
Feature #4256: Implement ToggleBorders (TB) console command Feature #4256: Implement ToggleBorders (TB) console command
Feature #4285: Support soundgen calls for activators
Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts
Feature #4345: Add equivalents for the command line commands to Launcher Feature #4345: Add equivalents for the command line commands to Launcher
Feature #4404: Editor: All EnumDelegate fields should have their items sorted alphabetically Feature #4404: Editor: All EnumDelegate fields should have their items sorted alphabetically
@ -172,6 +178,7 @@
Feature #4632: AI priority: utilize vanilla AI GMSTs for priority rating Feature #4632: AI priority: utilize vanilla AI GMSTs for priority rating
Feature #4636: Use sTo GMST in spellmaking menu Feature #4636: Use sTo GMST in spellmaking menu
Feature #4642: Batching potion creation Feature #4642: Batching potion creation
Feature #4682: Use the collision box from basic creature mesh if the X one have no collisions
Task #2490: Don't open command prompt window on Release-mode builds automatically Task #2490: Don't open command prompt window on Release-mode builds automatically
Task #4545: Enable is_pod string test Task #4545: Enable is_pod string test
Task #4605: Optimize skinning Task #4605: Optimize skinning

View file

@ -262,8 +262,8 @@ namespace MWBase
///< Adjust position after load to be on ground. Must be called after model load. ///< Adjust position after load to be on ground. Must be called after model load.
/// @param force do this even if the ptr is flying /// @param force do this even if the ptr is flying
virtual void fixPosition (const MWWorld::Ptr& actor) = 0; virtual void fixPosition () = 0;
///< Attempt to fix position so that the Ptr is no longer inside collision geometry. ///< Attempt to fix position so that the player is not stuck inside the geometry.
/// @note No-op for items in containers. Use ContainerStore::removeItem instead. /// @note No-op for items in containers. Use ContainerStore::removeItem instead.
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
@ -297,9 +297,11 @@ namespace MWBase
///< Queues movement for \a ptr (in local space), to be applied in the next call to ///< Queues movement for \a ptr (in local space), to be applied in the next call to
/// doPhysics. /// doPhysics.
virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors=false) = 0; virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask) = 0;
///< cast a Ray and return true if there is an object in the ray path. ///< cast a Ray and return true if there is an object in the ray path.
virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0;
virtual bool toggleCollisionMode() = 0; virtual bool toggleCollisionMode() = 0;
///< Toggle collision mode for player. If disabled player object should ignore ///< Toggle collision mode for player. If disabled player object should ignore
/// collisions and gravity. /// collisions and gravity.

View file

@ -1,6 +1,7 @@
#include "activator.hpp" #include "activator.hpp"
#include <components/esm/loadacti.hpp> #include <components/esm/loadacti.hpp>
#include <components/misc/rng.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -134,4 +135,60 @@ namespace MWClass
return MWWorld::Ptr(cell.insert(ref), &cell); return MWWorld::Ptr(cell.insert(ref), &cell);
} }
std::string Activator::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
{
std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
std::string creatureId;
for (const ESM::Creature &iter : store.get<ESM::Creature>())
{
if (!iter.mModel.empty() && Misc::StringUtils::ciEqual(model, "meshes\\" + iter.mModel))
{
creatureId = !iter.mOriginal.empty() ? iter.mOriginal : iter.mId;
break;
}
}
if (creatureId.empty())
return std::string();
int type = getSndGenTypeFromName(name);
std::vector<const ESM::SoundGenerator*> sounds;
for (auto sound = store.get<ESM::SoundGenerator>().begin(); sound != store.get<ESM::SoundGenerator>().end(); ++sound)
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature)))
sounds.push_back(&*sound);
if (!sounds.empty())
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
if (type == ESM::SoundGenerator::Land)
return "Body Fall Large";
return std::string();
}
int Activator::getSndGenTypeFromName(const std::string &name)
{
if (name == "left")
return ESM::SoundGenerator::LeftFoot;
if (name == "right")
return ESM::SoundGenerator::RightFoot;
if (name == "swimleft")
return ESM::SoundGenerator::SwimLeft;
if (name == "swimright")
return ESM::SoundGenerator::SwimRight;
if (name == "moan")
return ESM::SoundGenerator::Moan;
if (name == "roar")
return ESM::SoundGenerator::Roar;
if (name == "scream")
return ESM::SoundGenerator::Scream;
if (name == "land")
return ESM::SoundGenerator::Land;
throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
}
} }

View file

@ -10,6 +10,8 @@ namespace MWClass
virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const;
static int getSndGenTypeFromName(const std::string &name);
public: public:
virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const;
@ -44,6 +46,8 @@ namespace MWClass
///< Whether or not to use animated variant of model (default false) ///< Whether or not to use animated variant of model (default false)
virtual bool isActivator() const; virtual bool isActivator() const;
virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const;
}; };
} }

View file

@ -688,9 +688,9 @@ namespace MWClass
MWBase::World *world = MWBase::Environment::get().getWorld(); MWBase::World *world = MWBase::Environment::get().getWorld();
osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3());
if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr))
return 2; return ESM::SoundGenerator::SwimLeft;
if(world->isOnGround(ptr)) if(world->isOnGround(ptr))
return 0; return ESM::SoundGenerator::LeftFoot;
return -1; return -1;
} }
if(name == "right") if(name == "right")
@ -698,23 +698,23 @@ namespace MWClass
MWBase::World *world = MWBase::Environment::get().getWorld(); MWBase::World *world = MWBase::Environment::get().getWorld();
osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3());
if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr))
return 3; return ESM::SoundGenerator::SwimRight;
if(world->isOnGround(ptr)) if(world->isOnGround(ptr))
return 1; return ESM::SoundGenerator::RightFoot;
return -1; return -1;
} }
if(name == "swimleft") if(name == "swimleft")
return 2; return ESM::SoundGenerator::SwimLeft;
if(name == "swimright") if(name == "swimright")
return 3; return ESM::SoundGenerator::SwimRight;
if(name == "moan") if(name == "moan")
return 4; return ESM::SoundGenerator::Moan;
if(name == "roar") if(name == "roar")
return 5; return ESM::SoundGenerator::Roar;
if(name == "scream") if(name == "scream")
return 6; return ESM::SoundGenerator::Scream;
if(name == "land") if(name == "land")
return 7; return ESM::SoundGenerator::Land;
throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
} }

View file

@ -1242,15 +1242,9 @@ namespace MWClass
return ""; return "";
} }
// Morrowind ignores land soundgen for NPCs
if(name == "land") if(name == "land")
{ return "";
MWBase::World *world = MWBase::Environment::get().getWorld();
osg::Vec3f pos(ptr.getRefData().getPosition().asVec3());
if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr))
return "DefaultLandWater";
return "DefaultLand";
}
if(name == "swimleft") if(name == "swimleft")
return "Swim Left"; return "Swim Left";
if(name == "swimright") if(name == "swimright")

View file

@ -506,7 +506,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter
while (!stream.eof () && !ucsLineBreak (stream.peek ()) && ucsBreakingSpace (stream.peek ())) while (!stream.eof () && !ucsLineBreak (stream.peek ()) && ucsBreakingSpace (stream.peek ()))
{ {
MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek());
if (info.codePoint >= 0) if (info.charFound)
space_width += static_cast<int>(info.advance + info.bearingX); space_width += static_cast<int>(info.advance + info.bearingX);
stream.consume (); stream.consume ();
} }
@ -516,7 +516,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter
while (!stream.eof () && !ucsLineBreak (stream.peek ()) && !ucsBreakingSpace (stream.peek ())) while (!stream.eof () && !ucsLineBreak (stream.peek ()) && !ucsBreakingSpace (stream.peek ()))
{ {
MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek());
if (info.codePoint >= 0) if (info.charFound)
word_width += static_cast<int>(info.advance + info.bearingX); word_width += static_cast<int>(info.advance + info.bearingX);
stream.consume (); stream.consume ();
} }
@ -765,7 +765,7 @@ namespace
{ {
MWGui::GlyphInfo info = GlyphInfo(mFont, ch); MWGui::GlyphInfo info = GlyphInfo(mFont, ch);
if (info.codePoint < 0) if (!info.charFound)
return; return;
MyGUI::FloatRect vr; MyGUI::FloatRect vr;
@ -787,7 +787,7 @@ namespace
{ {
MWGui::GlyphInfo info = GlyphInfo(mFont, ch); MWGui::GlyphInfo info = GlyphInfo(mFont, ch);
if (info.codePoint >= 0) if (info.charFound)
mCursor.left += static_cast<int>(info.bearingX + info.advance); mCursor.left += static_cast<int>(info.bearingX + info.advance);
} }

View file

@ -43,6 +43,7 @@ namespace MWGui
float advance; float advance;
float bearingX; float bearingX;
float bearingY; float bearingY;
bool charFound;
MyGUI::FloatRect uvRect; MyGUI::FloatRect uvRect;
GlyphInfo(MyGUI::IFont* font, MyGUI::Char ch) GlyphInfo(MyGUI::IFont* font, MyGUI::Char ch)
@ -61,15 +62,17 @@ namespace MWGui
height = (int) gi->height / scale; height = (int) gi->height / scale;
advance = (int) gi->advance / scale; advance = (int) gi->advance / scale;
uvRect = gi->uvRect; uvRect = gi->uvRect;
charFound = true;
} }
else else
{ {
codePoint = -1; codePoint = 0;
bearingX = 0; bearingX = 0;
bearingY = 0; bearingY = 0;
width = 0; width = 0;
height = 0; height = 0;
advance = 0; advance = 0;
charFound = false;
} }
} }
}; };

View file

@ -169,19 +169,18 @@ namespace MWGui
// We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound()
mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback);
mShowWallpaper = visible && (MWBase::Environment::get().getStateManager()->getState() mVisible = visible;
== MWBase::StateManager::State_NoGame); mLoadingBox->setVisible(mVisible);
setVisible(true);
if (!visible) if (!mVisible)
{ {
mShowWallpaper = false;
draw(); draw();
return; return;
} }
mVisible = visible; mShowWallpaper = MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame;
mLoadingBox->setVisible(mVisible);
setVisible(true);
if (mShowWallpaper) if (mShowWallpaper)
{ {

View file

@ -4,6 +4,10 @@
#include <components/esm/aisequence.hpp> #include <components/esm/aisequence.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwphysics/collisiontype.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
@ -456,7 +460,48 @@ namespace MWMechanics
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
mCombatMove = true; mCombatMove = true;
} }
else if (isDistantCombat)
{
// Backing up behaviour
// Actor backs up slightly further away than opponent's weapon range
// (in vanilla - only as far as oponent's weapon range),
// or not at all if opponent is using a ranged weapon
if (targetUsesRanged || distToTarget > rangeAttackOfTarget*1.5) // Don't back up if the target is wielding ranged weapon
return;
// actor should not back up into water
if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.5f))
return;
int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door;
// Actor can not back up if there is no free space behind
// Currently we take the 35% of actor's height from the ground as vector height.
// This approach allows us to detect small obstacles (e.g. crates) and curved walls.
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
osg::Vec3f pos = actor.getRefData().getPosition().asVec3();
osg::Vec3f source = pos + osg::Vec3f(0, 0, 0.75f * halfExtents.z());
osg::Vec3f fallbackDirection = actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,-1,0);
osg::Vec3f destination = source + fallbackDirection * (halfExtents.y() + 16);
bool isObstacleDetected = MWBase::Environment::get().getWorld()->castRay(source.x(), source.y(), source.z(), destination.x(), destination.y(), destination.z(), mask);
if (isObstacleDetected)
return;
// Check if there is nothing behind - probably actor is near cliff.
// A current approach: cast ray 1.5-yard ray down in 1.5 yard behind actor from 35% of actor's height.
// If we did not hit anything, there is a cliff behind actor.
source = pos + osg::Vec3f(0, 0, 0.75f * halfExtents.z()) + fallbackDirection * (halfExtents.y() + 96);
destination = source - osg::Vec3f(0, 0, 0.75f * halfExtents.z() + 96);
bool isCliffDetected = !MWBase::Environment::get().getWorld()->castRay(source.x(), source.y(), source.z(), destination.x(), destination.y(), destination.z(), mask);
if (isCliffDetected)
return;
mMovement.mPosition[1] = -1;
}
// dodge movements (for NPCs and bipedal creatures) // dodge movements (for NPCs and bipedal creatures)
// 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 // apply sideway movement (kind of dodging) with some probability
@ -468,20 +513,6 @@ namespace MWMechanics
mCombatMove = true; mCombatMove = true;
} }
} }
// Backing up behaviour
// Actor backs up slightly further away than opponent's weapon range
// (in vanilla - only as far as oponent's weapon range),
// or not at all if opponent is using a ranged weapon
if (isDistantCombat)
{
// actor should not back up into water
if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.5f))
return;
if (!targetUsesRanged && distToTarget <= rangeAttackOfTarget*1.5) // Don't back up if the target is wielding ranged weapon
mMovement.mPosition[1] = -1;
}
} }
void AiCombatStorage::updateCombatMove(float duration) void AiCombatStorage::updateCombatMove(float duration)

View file

@ -979,17 +979,13 @@ void CharacterController::handleTextKey(const std::string &groupname, const std:
} }
} }
if (soundgen == "land") // Morrowind ignores land soundgen for some reason
return;
std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen);
if(!sound.empty()) if(!sound.empty())
{ {
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(soundgen == "left" || soundgen == "right") // NB: landing sound is not played for NPCs here
if(soundgen == "left" || soundgen == "right" || soundgen == "land")
{ {
// Don't make foot sounds local for the player, it makes sense to keep them
// positioned on the ground.
sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot, sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot,
MWSound::PlayMode::NoPlayerLocal); MWSound::PlayMode::NoPlayerLocal);
} }
@ -1282,6 +1278,18 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell &&
mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell;
// If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires),
// we should force actor to the "weapon equipped" state, interrupt attack and update animations.
if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperCharState_WeapEquiped)
{
forcestateupdate = true;
mUpperBodyState = UpperCharState_WeapEquiped;
mAttackingOrSpell = false;
mAnimation->disable(mCurrentWeapon);
if (mPtr == getPlayer())
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
}
if(!isKnockedOut() && !isKnockedDown() && !isRecovery()) if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
{ {
std::string weapgroup; std::string weapgroup;
@ -2071,12 +2079,18 @@ void CharacterController::update(float duration)
} }
} }
// Play landing sound // Play landing sound for NPCs
if (mPtr.getClass().isNpc())
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
std::string sound = cls.getSoundIdFromSndGen(mPtr, "land"); std::string sound = "DefaultLand";
if (!sound.empty()) osg::Vec3f pos(mPtr.getRefData().getPosition().asVec3());
if (world->isUnderwater(mPtr.getCell(), pos) || world->isWalkingOnWater(mPtr))
sound = "DefaultLandWater";
sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal);
} }
}
else else
{ {
jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None; jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None;

View file

@ -5,6 +5,8 @@
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwphysics/collisiontype.hpp"
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "pathgrid.hpp" #include "pathgrid.hpp"
@ -246,8 +248,9 @@ namespace MWMechanics
converter.toWorld(temp); converter.toWorld(temp);
// Add Z offset since path node can overlap with other objects. // Add Z offset since path node can overlap with other objects.
// Also ignore doors in raytesting. // Also ignore doors in raytesting.
int mask = MWPhysics::CollisionType_World;
bool isPathClear = !MWBase::Environment::get().getWorld()->castRay( bool isPathClear = !MWBase::Environment::get().getWorld()->castRay(
startPoint.mX, startPoint.mY, startPoint.mZ+16, temp.mX, temp.mY, temp.mZ+16, true); startPoint.mX, startPoint.mY, startPoint.mZ+16, temp.mX, temp.mY, temp.mZ+16, mask);
if (isPathClear) if (isPathClear)
mPath.pop_front(); mPath.pop_front();
} }

View file

@ -1337,6 +1337,16 @@ namespace MWPhysics
if (!shape) if (!shape)
return; return;
// Try to get shape from basic model as fallback for creatures
if (!ptr.getClass().isNpc() && shape->mCollisionBoxHalfExtents.length2() == 0)
{
const std::string fallbackModel = ptr.getClass().getModel(ptr);
if (fallbackModel != mesh)
{
shape = mShapeManager->getShape(fallbackModel);
}
}
Actor* actor = new Actor(ptr, shape, mCollisionWorld); Actor* actor = new Actor(ptr, shape, mCollisionWorld);
mActors.insert(std::make_pair(ptr, actor)); mActors.insert(std::make_pair(ptr, actor));
} }

View file

@ -862,13 +862,8 @@ namespace MWRender
screenshotCamera->addChild(quad); screenshotCamera->addChild(quad);
mRootNode->addChild(screenshotCamera);
renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH); renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH);
screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren());
mRootNode->removeChild(screenshotCamera);
return true; return true;
} }
@ -893,6 +888,8 @@ namespace MWRender
image->setDataType(GL_UNSIGNED_BYTE); image->setDataType(GL_UNSIGNED_BYTE);
image->setPixelFormat(texture->getInternalFormat()); image->setPixelFormat(texture->getInternalFormat());
mRootNode->addChild(camera);
// The draw needs to complete before we can copy back our image. // The draw needs to complete before we can copy back our image.
osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback); osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback);
camera->setFinalDrawCallback(callback); camera->setFinalDrawCallback(callback);
@ -908,32 +905,17 @@ namespace MWRender
// now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
camera->removeChildren(0, camera->getNumChildren());
mRootNode->removeChild(camera);
} }
void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform)
{ {
osg::ref_ptr<osg::Camera> rttCamera (new osg::Camera); osg::ref_ptr<osg::Camera> rttCamera (new osg::Camera);
rttCamera->setNodeMask(Mask_RenderToTexture);
rttCamera->attach(osg::Camera::COLOR_BUFFER, image);
rttCamera->setRenderOrder(osg::Camera::PRE_RENDER);
rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance);
rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform);
rttCamera->setViewport(0, 0, w, h);
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
texture->setInternalFormat(GL_RGB);
texture->setTextureSize(w, h);
texture->setResizeNonPowerOfTwoHint(false);
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
rttCamera->attach(osg::Camera::COLOR_BUFFER, texture);
image->setDataType(GL_UNSIGNED_BYTE);
image->setPixelFormat(texture->getInternalFormat());
rttCamera->setUpdateCallback(new NoTraverseCallback); rttCamera->setUpdateCallback(new NoTraverseCallback);
rttCamera->addChild(mSceneRoot); rttCamera->addChild(mSceneRoot);
@ -942,14 +924,9 @@ namespace MWRender
rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI));
mRootNode->addChild(rttCamera);
rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderCameraToImage(rttCamera.get(),image,w,h); renderCameraToImage(rttCamera.get(),image,w,h);
rttCamera->removeChildren(0, rttCamera->getNumChildren());
mRootNode->removeChild(rttCamera);
} }
osg::Vec4f RenderingManager::getScreenBounds(const MWWorld::Ptr& ptr) osg::Vec4f RenderingManager::getScreenBounds(const MWWorld::Ptr& ptr)

View file

@ -738,8 +738,7 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime) virtual void execute (Interpreter::Runtime& runtime)
{ {
const MWWorld::Ptr ptr = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->fixPosition();
MWBase::Environment::get().getWorld()->fixPosition(ptr);
} }
}; };

View file

@ -244,8 +244,11 @@ namespace MWSound
} }
DecoderPtr SoundManager::loadVoice(const std::string &voicefile) DecoderPtr SoundManager::loadVoice(const std::string &voicefile)
{
try
{ {
DecoderPtr decoder = getDecoder(); DecoderPtr decoder = getDecoder();
// Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav.
if(mVFS->exists(voicefile)) if(mVFS->exists(voicefile))
decoder->open(voicefile); decoder->open(voicefile);
@ -260,6 +263,13 @@ namespace MWSound
return decoder; return decoder;
} }
catch(std::exception &e)
{
Log(Debug::Error) << "Failed to load audio from " << voicefile << ": " << e.what();
}
return nullptr;
}
Sound *SoundManager::getSoundRef() Sound *SoundManager::getSoundRef()
{ {
@ -471,6 +481,8 @@ namespace MWSound
mVFS->normalizeFilename(voicefile); mVFS->normalizeFilename(voicefile);
DecoderPtr decoder = loadVoice(voicefile); DecoderPtr decoder = loadVoice(voicefile);
if (!decoder)
return;
MWBase::World *world = MWBase::Environment::get().getWorld(); MWBase::World *world = MWBase::Environment::get().getWorld();
const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans();
@ -503,6 +515,8 @@ namespace MWSound
mVFS->normalizeFilename(voicefile); mVFS->normalizeFilename(voicefile);
DecoderPtr decoder = loadVoice(voicefile); DecoderPtr decoder = loadVoice(voicefile);
if (!decoder)
return;
stopSay(MWWorld::ConstPtr()); stopSay(MWWorld::ConstPtr());
Stream *sound = playVoice(decoder, osg::Vec3f(), true); Stream *sound = playVoice(decoder, osg::Vec3f(), true);

View file

@ -117,7 +117,7 @@ namespace MWSound
Sound_Buffer *lookupSound(const std::string &soundId) const; Sound_Buffer *lookupSound(const std::string &soundId) const;
Sound_Buffer *loadSound(const std::string &soundId); Sound_Buffer *loadSound(const std::string &soundId);
// returns a decoder to start streaming // returns a decoder to start streaming, or nullptr if the sound was not found
DecoderPtr loadVoice(const std::string &voicefile); DecoderPtr loadVoice(const std::string &voicefile);
Sound *getSoundRef(); Sound *getSoundRef();

View file

@ -1351,8 +1351,9 @@ namespace MWWorld
moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z());
} }
void World::fixPosition(const Ptr &actor) void World::fixPosition()
{ {
const MWWorld::Ptr actor = getPlayerPtr();
const float distance = 128.f; const float distance = 128.f;
ESM::Position esmPos = actor.getRefData().getPosition(); ESM::Position esmPos = actor.getRefData().getPosition();
osg::Quat orientation(esmPos.rot[2], osg::Vec3f(0,0,-1)); osg::Quat orientation(esmPos.rot[2], osg::Vec3f(0,0,-1));
@ -1382,7 +1383,7 @@ namespace MWWorld
esmPos.pos[0] = traced.x(); esmPos.pos[0] = traced.x();
esmPos.pos[1] = traced.y(); esmPos.pos[1] = traced.y();
esmPos.pos[2] = traced.z(); esmPos.pos[2] = traced.z();
MWWorld::ActionTeleport("", esmPos, false).execute(actor); MWWorld::ActionTeleport(actor.getCell()->isExterior() ? "" : actor.getCell()->getCell()->mName, esmPos, false).execute(actor);
} }
} }
@ -1505,15 +1506,18 @@ namespace MWWorld
moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false);
} }
bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors) bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2)
{
int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_Door;
bool result = castRay(x1, y1, z1, x2, y2, z2, mask);
return result;
}
bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask)
{ {
osg::Vec3f a(x1,y1,z1); osg::Vec3f a(x1,y1,z1);
osg::Vec3f b(x2,y2,z2); osg::Vec3f b(x2,y2,z2);
int mask = MWPhysics::CollisionType_World;
if (!ignoreDoors)
mask |= MWPhysics::CollisionType_Door;
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector<MWWorld::Ptr>(), mask); MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector<MWWorld::Ptr>(), mask);
return result.mHit; return result.mHit;
} }

View file

@ -291,8 +291,8 @@ namespace MWWorld
///< Adjust position after load to be on ground. Must be called after model load. ///< Adjust position after load to be on ground. Must be called after model load.
/// @param force do this even if the ptr is flying /// @param force do this even if the ptr is flying
void fixPosition (const Ptr& actor) override; void fixPosition () override;
///< Attempt to fix position so that the Ptr is no longer inside collision geometry. ///< Attempt to fix position so that the player is not stuck inside the geometry.
void enable (const Ptr& ptr) override; void enable (const Ptr& ptr) override;
@ -402,9 +402,11 @@ namespace MWWorld
///< Queues movement for \a ptr (in local space), to be applied in the next call to ///< Queues movement for \a ptr (in local space), to be applied in the next call to
/// doPhysics. /// doPhysics.
bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors=false) override; bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask) override;
///< cast a Ray and return true if there is an object in the ray path. ///< cast a Ray and return true if there is an object in the ray path.
bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override;
bool toggleCollisionMode() override; bool toggleCollisionMode() override;
///< Toggle collision mode for player. If disabled player object should ignore ///< Toggle collision mode for player. If disabled player object should ignore
/// collisions and gravity. /// collisions and gravity.

View file

@ -8,12 +8,13 @@ namespace Debug
{ {
enum Level enum Level
{ {
NoLevel = 0,
Error = 1, Error = 1,
Warning = 2, Warning = 2,
Info = 3, Info = 3,
Verbose = 4, Verbose = 4,
Marker = Verbose Marker = Verbose,
NoLevel = 5 // Do not filter messages in this case
}; };
extern Level CurrentDebugLevel; extern Level CurrentDebugLevel;
@ -30,6 +31,11 @@ public:
mLock(sLock), mLock(sLock),
mLevel(level) mLevel(level)
{ {
// If the app has no logging system enabled, log level is not specified.
// Show all messages without marker - we just use the plain cout in this case.
if (Debug::CurrentDebugLevel == Debug::NoLevel)
return;
if (mLevel <= Debug::CurrentDebugLevel) if (mLevel <= Debug::CurrentDebugLevel)
std::cout << static_cast<unsigned char>(mLevel); std::cout << static_cast<unsigned char>(mLevel);
} }

View file

@ -30,6 +30,12 @@ namespace ESM
// The tmp buffer is a null-byte separated string list, we // The tmp buffer is a null-byte separated string list, we
// just have to pick out one string at a time. // just have to pick out one string at a time.
char* str = &tmp[0]; char* str = &tmp[0];
if (!str && mVarNames.size() > 0)
{
Log(Debug::Warning) << "SCVR with no variable names";
return;
}
for (size_t i = 0; i < mVarNames.size(); i++) for (size_t i = 0; i < mVarNames.size(); i++)
{ {
// Support '\r' terminated strings like vanilla. See Bug #1324. // Support '\r' terminated strings like vanilla. See Bug #1324.

View file

@ -1,5 +1,7 @@
#include "transport.hpp" #include "transport.hpp"
#include <components/debug/debuglog.hpp>
#include <components/esm/esmreader.hpp> #include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp> #include <components/esm/esmwriter.hpp>
@ -16,7 +18,11 @@ namespace ESM
} }
else if (esm.retSubName().intval == ESM::FourCC<'D','N','A','M'>::value) else if (esm.retSubName().intval == ESM::FourCC<'D','N','A','M'>::value)
{ {
mList.back().mCellName = esm.getHString(); const std::string name = esm.getHString();
if (mList.empty())
Log(Debug::Warning) << "Encountered DNAM record without DODT record, skipped.";
else
mList.back().mCellName = name;
} }
} }

View file

@ -169,8 +169,8 @@ def setup(app):
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static', html_static_path = [
'manuals/openmw-cs/_static' '_static'
] ]
# Add any extra paths that contain custom files (such as robots.txt or # Add any extra paths that contain custom files (such as robots.txt or

View file

@ -8,10 +8,26 @@ If you are familiar with ``.ini`` tweaks in Morrowind or the other games, this w
All settings described in this section are changed in ``settings.cfg``, located in your OpenMW user directory. All settings described in this section are changed in ``settings.cfg``, located in your OpenMW user directory.
See :doc:`../paths` for this location. See :doc:`../paths` for this location.
Changing Settings
#################
#. Once you have located your ``settings.cfg`` file, open it in a plain text editor.
#. Find the setting(s) you wish to change in the following pages.
#. If the setting is not already in ``settings.cfg``,
add it by copy and pasting the name exactly as written in this guide.
#. Set the value of the setting by typing ``= <value>`` after the setting on the same line,
using an appropriate value in place of ``<value>``.
#. If this is the first setting from it's category that you're adding,
be sure to add the heading in square brackets ``[]`` above it using just the setting type,
i.e. without the word "Settings".
For example, to delay tooltips popping up by 1 second, add the line ``tooltip delay = 1.0``.
Then to the line above, type ``[GUI]``, as the tooltip delay setting comes from the "GUI Settings" section.
Although this guide attempts to be comprehensive and up to date, Although this guide attempts to be comprehensive and up to date,
you will always be able to find the full list of settings available and their default values in ``settings-default.cfg`` you will always be able to find the full list of settings available and their default values in ``settings-default.cfg``
in your main OpenMW installation directory. in your main OpenMW installation directory.
The ranges I have included with each setting are the physically possible ranges, not recommendations. The ranges included with each setting are the physically possible ranges, not recommendations.
.. warning:: .. warning::
As the title suggests, these are advanced settings. As the title suggests, these are advanced settings.

View file

@ -1,5 +1,5 @@
Shader Settings Shaders Settings
############### ################
.. _force-shaders-label: .. _force-shaders-label:
force shaders force shaders