diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad7e9aa6e..b707851deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -130,15 +130,20 @@ Bug #4633: Sneaking stance affects speed even if the actor is not able to crouch Bug #4641: GetPCJumping is handled incorrectly 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 #4649: Levelup fully restores health 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 #4656: Combat AI: back up behaviour is incorrect Bug #4668: Editor: Light source color is displayed as an integer Bug #4669: ToggleCollision should trace the player down after collision being enabled Bug #4671: knownEffect functions should use modified Alchemy skill Bug #4672: Pitch factor is handled incorrectly for crossbow animations 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 #1221: Editor: Creature/NPC rendering Feature #1617: Editor: Enchantment effect record verifier @@ -153,6 +158,7 @@ Feature #4012: Editor: Write a log file if OpenCS crashes Feature #4222: 360° screenshots 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 #4345: Add equivalents for the command line commands to Launcher 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 #4636: Use sTo GMST in spellmaking menu 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 #4545: Enable is_pod string test Task #4605: Optimize skinning diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index e17935abcd..8da3e61125 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -262,8 +262,8 @@ namespace MWBase ///< Adjust position after load to be on ground. Must be called after model load. /// @param force do this even if the ptr is flying - virtual void fixPosition (const MWWorld::Ptr& actor) = 0; - ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. + virtual void fixPosition () = 0; + ///< 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. 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 /// 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. + virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; + virtual bool toggleCollisionMode() = 0; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 8df262f240..e0e2013912 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -1,6 +1,7 @@ #include "activator.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -134,4 +135,60 @@ namespace MWClass 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()) + { + 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 sounds; + + for (auto sound = store.get().begin(); sound != store.get().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); + } } diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 3f333f4cbe..b92dc75cbd 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -10,6 +10,8 @@ namespace MWClass virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; + static int getSndGenTypeFromName(const std::string &name); + public: 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) virtual bool isActivator() const; + + virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 6d0b42bfeb..788e5cd689 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -688,9 +688,9 @@ namespace MWClass MWBase::World *world = MWBase::Environment::get().getWorld(); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) - return 2; + return ESM::SoundGenerator::SwimLeft; if(world->isOnGround(ptr)) - return 0; + return ESM::SoundGenerator::LeftFoot; return -1; } if(name == "right") @@ -698,23 +698,23 @@ namespace MWClass MWBase::World *world = MWBase::Environment::get().getWorld(); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) - return 3; + return ESM::SoundGenerator::SwimRight; if(world->isOnGround(ptr)) - return 1; + return ESM::SoundGenerator::RightFoot; return -1; } if(name == "swimleft") - return 2; + return ESM::SoundGenerator::SwimLeft; if(name == "swimright") - return 3; + return ESM::SoundGenerator::SwimRight; if(name == "moan") - return 4; + return ESM::SoundGenerator::Moan; if(name == "roar") - return 5; + return ESM::SoundGenerator::Roar; if(name == "scream") - return 6; + return ESM::SoundGenerator::Scream; if(name == "land") - return 7; + return ESM::SoundGenerator::Land; throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4339f37e29..d6dafd2a25 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1242,15 +1242,9 @@ namespace MWClass return ""; } + // Morrowind ignores land soundgen for NPCs if(name == "land") - { - 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"; - } + return ""; if(name == "swimleft") return "Swim Left"; if(name == "swimright") diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 29dfe7f3a6..80e92f15ae 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -506,7 +506,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter while (!stream.eof () && !ucsLineBreak (stream.peek ()) && ucsBreakingSpace (stream.peek ())) { MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); - if (info.codePoint >= 0) + if (info.charFound) space_width += static_cast(info.advance + info.bearingX); stream.consume (); } @@ -516,7 +516,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter while (!stream.eof () && !ucsLineBreak (stream.peek ()) && !ucsBreakingSpace (stream.peek ())) { MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); - if (info.codePoint >= 0) + if (info.charFound) word_width += static_cast(info.advance + info.bearingX); stream.consume (); } @@ -765,7 +765,7 @@ namespace { MWGui::GlyphInfo info = GlyphInfo(mFont, ch); - if (info.codePoint < 0) + if (!info.charFound) return; MyGUI::FloatRect vr; @@ -787,7 +787,7 @@ namespace { MWGui::GlyphInfo info = GlyphInfo(mFont, ch); - if (info.codePoint >= 0) + if (info.charFound) mCursor.left += static_cast(info.bearingX + info.advance); } diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp index 4cf99794d7..4ea59414df 100644 --- a/apps/openmw/mwgui/bookpage.hpp +++ b/apps/openmw/mwgui/bookpage.hpp @@ -43,6 +43,7 @@ namespace MWGui float advance; float bearingX; float bearingY; + bool charFound; MyGUI::FloatRect uvRect; GlyphInfo(MyGUI::IFont* font, MyGUI::Char ch) @@ -61,15 +62,17 @@ namespace MWGui height = (int) gi->height / scale; advance = (int) gi->advance / scale; uvRect = gi->uvRect; + charFound = true; } else { - codePoint = -1; + codePoint = 0; bearingX = 0; bearingY = 0; width = 0; height = 0; advance = 0; + charFound = false; } } }; diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 38ab70d544..3bb3ee260d 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -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() mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); - mShowWallpaper = visible && (MWBase::Environment::get().getStateManager()->getState() - == MWBase::StateManager::State_NoGame); + mVisible = visible; + mLoadingBox->setVisible(mVisible); + setVisible(true); - if (!visible) + if (!mVisible) { + mShowWallpaper = false; draw(); return; } - mVisible = visible; - mLoadingBox->setVisible(mVisible); - - setVisible(true); + mShowWallpaper = MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; if (mShowWallpaper) { diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8f9545f99d..a96832b698 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -4,6 +4,10 @@ #include +#include + +#include "../mwphysics/collisiontype.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -456,7 +460,48 @@ namespace MWMechanics mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); 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) + // 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)) { // apply sideway movement (kind of dodging) with some probability @@ -468,20 +513,6 @@ namespace MWMechanics 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) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 7ad8d61cd2..ce844674f4 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -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); if(!sound.empty()) { 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, MWSound::PlayMode::NoPlayerLocal); } @@ -1282,6 +1278,18 @@ bool CharacterController::updateWeaponState(CharacterState& idle) bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < 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()) { std::string weapgroup; @@ -2071,11 +2079,17 @@ void CharacterController::update(float duration) } } - // Play landing sound - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - std::string sound = cls.getSoundIdFromSndGen(mPtr, "land"); - if (!sound.empty()) + // Play landing sound for NPCs + if (mPtr.getClass().isNpc()) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + std::string sound = "DefaultLand"; + 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); + } } else { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 1da97a6459..c16cff9e10 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -5,6 +5,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwphysics/collisiontype.hpp" + #include "../mwworld/cellstore.hpp" #include "pathgrid.hpp" @@ -246,8 +248,9 @@ namespace MWMechanics converter.toWorld(temp); // Add Z offset since path node can overlap with other objects. // Also ignore doors in raytesting. + int mask = MWPhysics::CollisionType_World; 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) mPath.pop_front(); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 363f28e70f..2ce498f377 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1337,6 +1337,16 @@ namespace MWPhysics if (!shape) 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); mActors.insert(std::make_pair(ptr, actor)); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c3e68aa680..5a7c87fe79 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -831,7 +831,7 @@ namespace MWRender cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); - + cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); @@ -856,19 +856,14 @@ namespace MWRender stateset->addUniform(new osg::Uniform("cubeMap",0)); stateset->addUniform(new osg::Uniform("mapping",screenshotMapping)); stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); - + quad->setStateSet(stateset); quad->setUpdateCallback(nullptr); screenshotCamera->addChild(quad); - mRootNode->addChild(screenshotCamera); - renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH); - screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); - mRootNode->removeChild(screenshotCamera); - return true; } @@ -893,6 +888,8 @@ namespace MWRender image->setDataType(GL_UNSIGNED_BYTE); image->setPixelFormat(texture->getInternalFormat()); + mRootNode->addChild(camera); + // The draw needs to complete before we can copy back our image. osg::ref_ptr callback (new NotifyDrawCompletedCallback); 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 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) { osg::ref_ptr 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->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); - rttCamera->setViewport(0, 0, w, h); - - osg::ref_ptr 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->addChild(mSceneRoot); @@ -942,14 +924,9 @@ namespace MWRender rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); - mRootNode->addChild(rttCamera); - rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderCameraToImage(rttCamera.get(),image,w,h); - - rttCamera->removeChildren(0, rttCamera->getNumChildren()); - mRootNode->removeChild(rttCamera); } osg::Vec4f RenderingManager::getScreenBounds(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 2695aed764..9f0784d6c0 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -738,8 +738,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - const MWWorld::Ptr ptr = MWMechanics::getPlayer(); - MWBase::Environment::get().getWorld()->fixPosition(ptr); + MWBase::Environment::get().getWorld()->fixPosition(); } }; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 8e3488906c..7b722a8352 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -245,20 +245,30 @@ namespace MWSound DecoderPtr SoundManager::loadVoice(const std::string &voicefile) { - DecoderPtr decoder = getDecoder(); - // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. - if(mVFS->exists(voicefile)) - decoder->open(voicefile); - else + try { - std::string file = voicefile; - std::string::size_type pos = file.rfind('.'); - if(pos != std::string::npos) - file = file.substr(0, pos)+".mp3"; - decoder->open(file); + DecoderPtr decoder = getDecoder(); + + // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. + if(mVFS->exists(voicefile)) + decoder->open(voicefile); + else + { + std::string file = voicefile; + std::string::size_type pos = file.rfind('.'); + if(pos != std::string::npos) + file = file.substr(0, pos)+".mp3"; + decoder->open(file); + } + + return decoder; + } + catch(std::exception &e) + { + Log(Debug::Error) << "Failed to load audio from " << voicefile << ": " << e.what(); } - return decoder; + return nullptr; } Sound *SoundManager::getSoundRef() @@ -471,6 +481,8 @@ namespace MWSound mVFS->normalizeFilename(voicefile); DecoderPtr decoder = loadVoice(voicefile); + if (!decoder) + return; MWBase::World *world = MWBase::Environment::get().getWorld(); const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); @@ -503,6 +515,8 @@ namespace MWSound mVFS->normalizeFilename(voicefile); DecoderPtr decoder = loadVoice(voicefile); + if (!decoder) + return; stopSay(MWWorld::ConstPtr()); Stream *sound = playVoice(decoder, osg::Vec3f(), true); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 4064a05afe..d8a4cfc8cc 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -117,7 +117,7 @@ namespace MWSound Sound_Buffer *lookupSound(const std::string &soundId) const; 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); Sound *getSoundRef(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0872e589dc..84dbe921ec 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1351,8 +1351,9 @@ namespace MWWorld 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; ESM::Position esmPos = actor.getRefData().getPosition(); osg::Quat orientation(esmPos.rot[2], osg::Vec3f(0,0,-1)); @@ -1382,7 +1383,7 @@ namespace MWWorld esmPos.pos[0] = traced.x(); esmPos.pos[1] = traced.y(); 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); } - 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 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(), mask); return result.mHit; } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 7df8d1af5b..3a42a7ee0a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -291,8 +291,8 @@ namespace MWWorld ///< Adjust position after load to be on ground. Must be called after model load. /// @param force do this even if the ptr is flying - void fixPosition (const Ptr& actor) override; - ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. + void fixPosition () override; + ///< Attempt to fix position so that the player is not stuck inside the geometry. 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 /// 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. + bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; + bool toggleCollisionMode() override; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. diff --git a/components/debug/debuglog.hpp b/components/debug/debuglog.hpp index f4a8e17bef..f5cdffeda1 100644 --- a/components/debug/debuglog.hpp +++ b/components/debug/debuglog.hpp @@ -8,12 +8,13 @@ namespace Debug { enum Level { - NoLevel = 0, Error = 1, Warning = 2, Info = 3, Verbose = 4, - Marker = Verbose + Marker = Verbose, + + NoLevel = 5 // Do not filter messages in this case }; extern Level CurrentDebugLevel; @@ -30,6 +31,11 @@ public: mLock(sLock), 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) std::cout << static_cast(mLevel); } diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 474c2b4234..808b416d7b 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -30,6 +30,12 @@ namespace ESM // The tmp buffer is a null-byte separated string list, we // just have to pick out one string at a time. 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++) { // Support '\r' terminated strings like vanilla. See Bug #1324. diff --git a/components/esm/transport.cpp b/components/esm/transport.cpp index 063c033299..11676ea723 100644 --- a/components/esm/transport.cpp +++ b/components/esm/transport.cpp @@ -1,5 +1,7 @@ #include "transport.hpp" +#include + #include #include @@ -16,7 +18,11 @@ namespace ESM } 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; } } diff --git a/docs/source/conf.py b/docs/source/conf.py index da59c02e1d..60b25ae57b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -169,8 +169,8 @@ def setup(app): # 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, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static', - 'manuals/openmw-cs/_static' +html_static_path = [ + '_static' ] # Add any extra paths that contain custom files (such as robots.txt or diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index a11df35a3d..b4bf1d2d37 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -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. 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 ``= `` after the setting on the same line, + using an appropriate value in place of ````. +#. 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, 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. -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:: As the title suggests, these are advanced settings. diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index dbce11464c..2d5ec5d36b 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -1,5 +1,5 @@ -Shader Settings -############### +Shaders Settings +################ .. _force-shaders-label: force shaders