From 292f4fcafe204be73543cfd862078bdc1e472815 Mon Sep 17 00:00:00 2001 From: rhtucker Date: Fri, 12 Oct 2018 21:09:27 -0700 Subject: [PATCH 01/31] Unified settings list and cfg file names. Added brief tutorial. --- docs/source/conf.py | 4 ++-- .../reference/modding/settings/index.rst | 18 +++++++++++++++++- .../reference/modding/settings/shaders.rst | 4 ++-- 3 files changed, 21 insertions(+), 5 deletions(-) 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 56d76a8d10..f7c86b5678 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 17d0929174..be1ecebf0c 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 ------------- From b5df385111241e14fcee20795b8c0a88431ffdbb Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 12 Oct 2018 14:11:41 +0400 Subject: [PATCH 02/31] Allow apps without logging system to display log messages --- components/debug/debuglog.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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); } From ca07e3a36449e09ee7f8346161e7cb6beb6ffe3b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 30 Sep 2018 08:38:55 +0400 Subject: [PATCH 03/31] Check for obstacle before back up (bug #4656) --- CHANGELOG.md | 1 + apps/openmw/mwbase/world.hpp | 4 +- apps/openmw/mwmechanics/aicombat.cpp | 59 +++++++++++++++++++------ apps/openmw/mwmechanics/pathfinding.cpp | 5 ++- apps/openmw/mwworld/worldimp.cpp | 13 +++--- apps/openmw/mwworld/worldimp.hpp | 4 +- 6 files changed, 64 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 320e486937..abed0ba91c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,7 @@ 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 diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index e17935abcd..027d1fd102 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -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/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/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/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0872e589dc..0f20fa05a1 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1505,15 +1505,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..1592453a20 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -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. From 19fd404b7ba0fd32e45473f91ae3c361758b7513 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 14 Oct 2018 14:44:32 +0300 Subject: [PATCH 04/31] Support soundgen calls for activators (feature #4285) --- CHANGELOG.md | 1 + apps/openmw/mwclass/activator.cpp | 70 +++++++++++++++++++++++++++++++ apps/openmw/mwclass/activator.hpp | 4 ++ 3 files changed, 75 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 320e486937..ddf20fa7f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -155,6 +155,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 diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 8df262f240..7f53709d6c 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,73 @@ 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); + if (model.empty()) + return std::string(); + + const MWWorld::Store &creaturestore = MWBase::Environment::get().getWorld()->getStore().get(); + std::string creatureId; + + for (const ESM::Creature &iter : creaturestore) + { + if (iter.mModel.empty()) + continue; + + if (Misc::StringUtils::ciEqual(model, "meshes\\" + iter.mModel)) + { + creatureId = !iter.mOriginal.empty() ? iter.mOriginal : iter.mId; + break; + } + } + + if (creatureId.empty()) + return std::string(); + + const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); + + int type = getSndGenTypeFromName(name); + std::vector sounds; + + MWWorld::Store::iterator sound = store.begin(); + + while (sound != store.end()) + { + if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature))) + sounds.push_back(&*sound); + ++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) const + { + if (name == "left") + return 0; + if (name == "right") + return 1; + if (name == "swimleft") + return 2; + if (name == "swimright") + return 3; + if (name == "moan") + return 4; + if (name == "roar") + return 5; + if (name == "scream") + return 6; + if (name == "land") + return 7; + + 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..0f66b74b45 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -44,6 +44,10 @@ 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; + + virtual int getSndGenTypeFromName(const std::string &name) const; }; } From 6ef7be3fd3e50925b26bd8d0cd2cb3b87e1846db Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 14 Oct 2018 20:11:21 +0300 Subject: [PATCH 05/31] Re-enable using soundgen land for creatures --- apps/openmw/mwclass/npc.cpp | 10 ++-------- apps/openmw/mwmechanics/character.cpp | 22 ++++++++++++---------- 2 files changed, 14 insertions(+), 18 deletions(-) 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/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 7ad8d61cd2..0931aeda12 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); } @@ -2071,11 +2067,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 { From bf3f82b9d4c6f259fe50fe12063b6838d688283a Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 14 Oct 2018 20:37:40 +0300 Subject: [PATCH 06/31] Cleanup --- apps/openmw/mwclass/activator.cpp | 43 +++++++++++-------------------- apps/openmw/mwclass/activator.hpp | 4 +-- apps/openmw/mwclass/creature.cpp | 20 +++++++------- 3 files changed, 27 insertions(+), 40 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 7f53709d6c..e0e2013912 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -138,19 +138,13 @@ namespace MWClass std::string Activator::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const { - std::string model = getModel(ptr); - if (model.empty()) - return std::string(); - - const MWWorld::Store &creaturestore = MWBase::Environment::get().getWorld()->getStore().get(); + 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 : creaturestore) - { - if (iter.mModel.empty()) - continue; - if (Misc::StringUtils::ciEqual(model, "meshes\\" + iter.mModel)) + 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; @@ -160,19 +154,12 @@ namespace MWClass if (creatureId.empty()) return std::string(); - const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); - int type = getSndGenTypeFromName(name); std::vector sounds; - MWWorld::Store::iterator sound = store.begin(); - - while (sound != store.end()) - { + 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); - ++sound; - } if (!sounds.empty()) return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; @@ -183,24 +170,24 @@ namespace MWClass return std::string(); } - int Activator::getSndGenTypeFromName(const std::string &name) const + int Activator::getSndGenTypeFromName(const std::string &name) { if (name == "left") - return 0; + return ESM::SoundGenerator::LeftFoot; if (name == "right") - return 1; + return ESM::SoundGenerator::RightFoot; 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/activator.hpp b/apps/openmw/mwclass/activator.hpp index 0f66b74b45..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; @@ -46,8 +48,6 @@ namespace MWClass virtual bool isActivator() const; virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const; - - virtual int getSndGenTypeFromName(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); } From 13bd81f8969808d6bd13fc4d24e375b67c2ab504 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 16 Oct 2018 22:28:19 +0400 Subject: [PATCH 07/31] Try to use collisions from basic actor model if an animated one has no collisions (feature #4682) --- CHANGELOG.md | 1 + apps/openmw/mwphysics/physicssystem.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddf20fa7f3..cda9150b5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -175,6 +175,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/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)); } From d7d9050d4ae558708a4a291a3e0709d14cee452a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 18 Oct 2018 11:42:03 +0400 Subject: [PATCH 08/31] Force actor to the 'weapon equipped' state if the weapon disappeared in the middle of attack (bug #4646) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4d5d34f7..44932f2ffe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -130,6 +130,7 @@ 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 diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 0931aeda12..ce844674f4 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1278,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; From 8fa0ffcfe4e0753651c1435fc1aed0dc735b6cce Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 18 Oct 2018 14:59:39 +0400 Subject: [PATCH 09/31] Catch exceptions inside the loadVoice() (bug #4685) --- CHANGELOG.md | 1 + apps/openmw/mwsound/soundmanagerimp.cpp | 36 +++++++++++++++++-------- apps/openmw/mwsound/soundmanagerimp.hpp | 2 +- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4d5d34f7..14abe86b74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -142,6 +142,7 @@ 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 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(); From 46bf45a6e2f0a30aa8f34c2a6c64406c36a5def7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Oct 2018 13:09:39 +0400 Subject: [PATCH 10/31] Remove redundant code --- apps/openmw/mwrender/animation.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6e2c76d1d8..f165b6bd3d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -190,12 +190,6 @@ namespace { } - RemoveFinishedCallbackVisitor(int effectId) - : RemoveVisitor() - , mHasMagicEffects(false) - { - } - virtual void apply(osg::Node &node) { traverse(node); @@ -228,9 +222,6 @@ namespace virtual void apply(osg::Geometry&) { } - - private: - int mEffectId; }; class RemoveCallbackVisitor : public RemoveVisitor From 41e90bd56c7e57f632d116f0f5cf10ec6c9906d8 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Oct 2018 14:37:25 +0400 Subject: [PATCH 11/31] Unify random generator usage --- apps/opencs/editor.cpp | 3 +++ components/nifosg/particle.cpp | 12 +++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 450b434e66..6699ccaaf5 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include "model/doc/document.hpp" @@ -355,6 +356,8 @@ int CS::Editor::run() if (mLocal.empty()) return 1; + Misc::Rng::init(); + mStartup.show(); QApplication::setQuitOnLastWindowClosed (true); diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index f5c055a15a..c1f6a2819b 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -81,17 +82,17 @@ ParticleShooter::ParticleShooter(const ParticleShooter ©, const osg::CopyOp void ParticleShooter::shoot(osgParticle::Particle *particle) const { - float hdir = mHorizontalDir + mHorizontalAngle * (2.f * (std::rand() / static_cast(RAND_MAX)) - 1.f); - float vdir = mVerticalDir + mVerticalAngle * (2.f * (std::rand() / static_cast(RAND_MAX)) - 1.f); + float hdir = mHorizontalDir + mHorizontalAngle * (2.f * Misc::Rng::rollClosedProbability() - 1.f); + float vdir = mVerticalDir + mVerticalAngle * (2.f * Misc::Rng::rollClosedProbability() - 1.f); osg::Vec3f dir = (osg::Quat(vdir, osg::Vec3f(0,1,0)) * osg::Quat(hdir, osg::Vec3f(0,0,1))) * osg::Vec3f(0,0,1); - float vel = mMinSpeed + (mMaxSpeed - mMinSpeed) * std::rand() / static_cast(RAND_MAX); + float vel = mMinSpeed + (mMaxSpeed - mMinSpeed) * Misc::Rng::rollClosedProbability(); particle->setVelocity(dir * vel); // Not supposed to set this here, but there doesn't seem to be a better way of doing it - particle->setLifeTime(mLifetime + mLifetimeRandom * std::rand() / static_cast(RAND_MAX)); + particle->setLifeTime(mLifetime + mLifetimeRandom * Misc::Rng::rollClosedProbability()); } GrowFadeAffector::GrowFadeAffector(float growTime, float fadeTime) @@ -277,7 +278,8 @@ void Emitter::emitParticles(double dt) if (!mTargets.empty()) { - int randomRecIndex = mTargets[(std::rand() / (static_cast(RAND_MAX)+1.0)) * mTargets.size()]; + int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1); + int randomRecIndex = mTargets[randomIndex]; // we could use a map here for faster lookup FindGroupByRecIndex visitor(randomRecIndex); From 229d1bb4258b48c274ca0315503f9b0e0812dbdc Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Oct 2018 19:43:19 +0400 Subject: [PATCH 12/31] Backport loop from tinyxml 2.6 to avoid CVE --- extern/oics/tinyxml.cpp | 59 +++++++++++++---------------------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/extern/oics/tinyxml.cpp b/extern/oics/tinyxml.cpp index f1cdc81925..b61df85c87 100644 --- a/extern/oics/tinyxml.cpp +++ b/extern/oics/tinyxml.cpp @@ -1046,58 +1046,35 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) return false; } - const char* lastPos = buf; - const char* p = buf; + const char* p = buf; // the read head + char* q = buf; // the write head + const char CR = 0x0d; + const char LF = 0x0a; buf[length] = 0; while( *p ) { assert( p < (buf+length) ); - if ( *p == 0xa ) { - // Newline character. No special rules for this. Append all the characters - // since the last string, and include the newline. - data.append( lastPos, (p-lastPos+1) ); // append, include the newline - ++p; // move past the newline - lastPos = p; // and point to the new buffer (may be 0) - assert( p <= (buf+length) ); - } - else if ( *p == 0xd ) { - // Carriage return. Append what we have so far, then - // handle moving forward in the buffer. - if ( (p-lastPos) > 0 ) { - data.append( lastPos, p-lastPos ); // do not add the CR - } - data += (char)0xa; // a proper newline + assert( q <= (buf+length) ); + assert( q <= p ); - if ( *(p+1) == 0xa ) { - // Carriage return - new line sequence - p += 2; - lastPos = p; - assert( p <= (buf+length) ); - } - else { - // it was followed by something else...that is presumably characters again. - ++p; - lastPos = p; - assert( p <= (buf+length) ); + if ( *p == CR ) { + *q++ = LF; + p++; + if ( *p == LF ) { // check for CR+LF (and skip LF) + p++; } } else { - ++p; + *q++ = *p++; } } - // Handle any left over characters. - if ( p-lastPos ) { - data.append( lastPos, p-lastPos ); - } + assert( q <= (buf+length) ); + *q = 0; + + Parse( buf, 0, encoding ); + delete [] buf; - buf = 0; - - Parse( data.c_str(), 0, encoding ); - - if ( Error() ) - return false; - else - return true; + return !Error(); } From 9809eef18e8587969a787f7e057157592fb8e963 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 20 Oct 2018 22:14:15 +0300 Subject: [PATCH 13/31] Utilize the default soundgen entries when necessary (bug #4689) --- CHANGELOG.md | 1 + apps/openmw/mwclass/activator.cpp | 38 +++++++++++++++++++++---------- apps/openmw/mwclass/creature.cpp | 12 ++++++---- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b707851deb..150c294fc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,6 +144,7 @@ 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 + Bug #4689: Default creature soundgen entries are not used Feature #912: Editor: Add missing icons to UniversalId tables Feature #1221: Editor: Creature/NPC rendering Feature #1617: Editor: Enchantment effect record verifier diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index e0e2013912..42ea99b007 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -138,7 +138,7 @@ namespace MWClass 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 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; @@ -151,21 +151,35 @@ namespace MWClass } } - 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); + std::vector fallbacksounds; + if (!creatureId.empty()) + { + 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 (type == sound->mType && sound->mCreature.empty()) + fallbacksounds.push_back(&*sound); + } - if (!sounds.empty()) - return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + if (!sounds.empty()) + return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + if (!fallbacksounds.empty()) + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; + } + else + { + // The activator doesn't have a corresponding creature ID, but we can try to use the defaults + for (auto sound = store.get().begin(); sound != store.get().end(); ++sound) + if (type == sound->mType && sound->mCreature.empty()) + fallbacksounds.push_back(&*sound); - if (type == ESM::SoundGenerator::Land) - return "Body Fall Large"; + if (!fallbacksounds.empty()) + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; + } return std::string(); } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 788e5cd689..e03635b0c5 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -632,25 +632,27 @@ namespace MWClass if(type >= 0) { std::vector sounds; + std::vector fallbacksounds; MWWorld::LiveCellRef* ref = ptr.get(); const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal; MWWorld::Store::iterator sound = store.begin(); - while(sound != store.end()) + while (sound != store.end()) { if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(ourId, sound->mCreature))) sounds.push_back(&*sound); + if (type == sound->mType && sound->mCreature.empty()) + fallbacksounds.push_back(&*sound); ++sound; } - if(!sounds.empty()) + if (!sounds.empty()) return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + if (!fallbacksounds.empty()) + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; } - if (type == ESM::SoundGenerator::Land) - return "Body Fall Large"; - return ""; } From 61e6e359c4595fd28f48dbb1a106931737c8d8c3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 22 Oct 2018 15:14:25 +0400 Subject: [PATCH 14/31] Allow creatures to use the autogenerated collision box (feature #2787) --- CHANGELOG.md | 1 + apps/openmw/mwphysics/actor.cpp | 26 ++++++++++++++++++++++++++ components/resource/bulletshape.hpp | 3 +-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b707851deb..a4e7364678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -149,6 +149,7 @@ Feature #1617: Editor: Enchantment effect record verifier Feature #1645: Casting effects from objects Feature #2606: Editor: Implemented (optional) case sensitive global search + Feature #2787: Use the autogenerated collision box, if the creature mesh has no predefined one Feature #2847: Content selector: allow to copy the path to a file by using the context menu Feature #3083: Play animation when NPC is casting spell via script Feature #3103: Provide option for disposition to get increased by successful trade diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 79c6dcabfc..b55c20455a 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "../mwworld/class.hpp" @@ -28,6 +29,31 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr mHalfExtents = shape->mCollisionBoxHalfExtents; mMeshTranslation = shape->mCollisionBoxTranslate; + // We can not create actor without collisions - he will fall through the ground. + // In this case we should autogenerate collision box based on mesh shape + // (NPCs have bodyparts and use a different approach) + if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f) + { + const Resource::BulletShape* collisionShape = shape.get(); + if (collisionShape && collisionShape->mCollisionShape) + { + btTransform transform; + transform.setIdentity(); + btVector3 min; + btVector3 max; + + collisionShape->mCollisionShape->getAabb(transform, min, max); + mHalfExtents.x() = (max[0] - min[0])/2.f; + mHalfExtents.y() = (max[1] - min[1])/2.f; + mHalfExtents.z() = (max[2] - min[2])/2.f; + + mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z()); + } + + if (mHalfExtents.length2() == 0.f) + Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\"."; + } + // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it) if (std::abs(mHalfExtents.x()-mHalfExtents.y())= mHalfExtents.x()) { diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index a418bb28ce..b30b5045c2 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -26,8 +26,7 @@ namespace Resource btCollisionShape* mCollisionShape; - // Used for actors. Note, ideally actors would use a separate loader - as it is - // we have to keep a redundant copy of the actor model around in mCollisionShape, which isn't used. + // Used for actors. mCollisionShape is used for actors only when we need to autogenerate collision box for creatures. // For now, use one file <-> one resource for simplicity. osg::Vec3f mCollisionBoxHalfExtents; osg::Vec3f mCollisionBoxTranslate; From 4ce35c6ad558cd25d9ddf5e1d1f94b8e84c476f0 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 24 Oct 2018 01:40:57 +0300 Subject: [PATCH 15/31] Fix fixme behavior in interiors --- apps/openmw/mwbase/world.hpp | 4 ++-- apps/openmw/mwscript/transformationextensions.cpp | 3 +-- apps/openmw/mwworld/worldimp.cpp | 5 +++-- apps/openmw/mwworld/worldimp.hpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 027d1fd102..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; 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/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0f20fa05a1..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); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1592453a20..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; From 67de61e1fb3bf14ef0b5f6bb1cecc3b6b4d3d363 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 24 Oct 2018 18:51:34 +0300 Subject: [PATCH 16/31] Avoid item condition and charge zero divisions --- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/npc.cpp | 11 +++++- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwgui/merchantrepair.cpp | 2 +- apps/openmw/mwgui/sortfilteritemmodel.cpp | 48 +++++++++++++++++++++-- apps/openmw/mwgui/tradewindow.cpp | 11 +++++- apps/openmw/mwgui/windowmanagerimp.cpp | 29 ++++++++++++-- apps/openmw/mwmechanics/combat.cpp | 11 +++++- 8 files changed, 100 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index b90c1ec581..ad64ad0d1e 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -297,7 +297,7 @@ namespace MWClass { const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); - if (ptr.getCellRef().getCharge() == 0) + if (getItemHealth(ptr) == 0) return std::make_pair(0, "#{sInventoryMessage1}"); // slots that this item can be equipped in diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d6dafd2a25..f7172ac0b9 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1148,9 +1148,16 @@ namespace MWClass const bool hasHealth = it->getClass().hasItemHealth(*it); if (hasHealth) { - int armorHealth = it->getClass().getItemHealth(*it); int armorMaxHealth = it->getClass().getItemMaxHealth(*it); - ratings[i] *= (float(armorHealth) / armorMaxHealth); + if (armorMaxHealth == 0) + { + ratings[i] = 0; + } + else + { + int armorHealth = it->getClass().getItemHealth(*it); + ratings[i] *= (float(armorHealth) / armorMaxHealth); + } } } } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 78678f4617..7d28c89835 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -383,7 +383,7 @@ namespace MWClass std::pair Weapon::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { - if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) + if (hasItemHealth(ptr) && getItemHealth(ptr) == 0) return std::make_pair(0, "#{sInventoryMessage1}"); // Do not allow equip weapons from inventory during attack diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index b9d4c80f4f..282c0e4ea2 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -51,7 +51,7 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &actor) { int maxDurability = iter->getClass().getItemMaxHealth(*iter); int durability = iter->getClass().getItemHealth(*iter); - if (maxDurability == durability) + if (maxDurability == durability || maxDurability == 0) continue; int basePrice = iter->getClass().getValue(*iter); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index fe7f619524..5172c85ee8 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -86,26 +86,66 @@ namespace if (!leftName.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(leftName); + if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) + { leftChargePercent = 101; + } else - leftChargePercent = (left.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100 - : static_cast(left.mBase.getCellRef().getEnchantmentCharge() / static_cast(ench->mData.mCharge) * 100); + { + int maxEnchCharge = ench->mData.mCharge; + if (maxEnchCharge == 0) + { + leftChargePercent = 0; + } + else + { + float enchCharge = left.mBase.getCellRef().getEnchantmentCharge(); + if (enchCharge == -1) + { + leftChargePercent = 100; + } + else + { + leftChargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); + } + } + } } } if (!rightName.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(rightName); + if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) + { rightChargePercent = 101; + } else - rightChargePercent = (right.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100 - : static_cast(right.mBase.getCellRef().getEnchantmentCharge() / static_cast(ench->mData.mCharge) * 100); + { + int maxEnchCharge = ench->mData.mCharge; + if (maxEnchCharge == 0) + { + rightChargePercent = 0; + } + else + { + float enchCharge = right.mBase.getCellRef().getEnchantmentCharge(); + if (enchCharge == -1) + { + rightChargePercent = 100; + } + else + { + rightChargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); + } + } + } } } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 4404b2b1a6..2fd91fd4a0 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -35,8 +35,15 @@ namespace float price = static_cast(item.getClass().getValue(item)); if (item.getClass().hasItemHealth(item)) { - price *= item.getClass().getItemHealth(item); - price /= item.getClass().getItemMaxHealth(item); + if (item.getClass().getItemMaxHealth(item) == 0) + { + price = 0; + } + else + { + price *= item.getClass().getItemHealth(item); + price /= item.getClass().getItemMaxHealth(item); + } } return static_cast(price * count); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index e4515fdc3f..3b26edecb8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1369,8 +1369,22 @@ namespace MWGui const ESM::Enchantment* ench = mStore->get() .find(item.getClass().getEnchantment(item)); - int chargePercent = (item.getCellRef().getEnchantmentCharge() == -1) ? 100 - : static_cast(item.getCellRef().getEnchantmentCharge() / static_cast(ench->mData.mCharge) * 100); + int chargePercent = 100; + + int maxEnchCharge = ench->mData.mCharge; + if (maxEnchCharge == 0) + { + chargePercent = 0; + } + else + { + float enchCharge = item.getCellRef().getEnchantmentCharge(); + if (enchCharge != -1) + { + chargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); + } + } + mHud->setSelectedEnchantItem(item, chargePercent); mSpellWindow->setTitle(item.getClass().getName(item)); } @@ -1386,7 +1400,16 @@ namespace MWGui int durabilityPercent = 100; if (item.getClass().hasItemHealth(item)) { - durabilityPercent = static_cast(item.getClass().getItemHealth(item) / static_cast(item.getClass().getItemMaxHealth(item)) * 100); + int weapmaxhealth = item.getClass().getItemMaxHealth(item); + if (weapmaxhealth == 0) + { + durabilityPercent = 0; + } + else + { + int weaphealth = item.getClass().getItemHealth(item); + durabilityPercent = static_cast(weaphealth / static_cast(weapmaxhealth) * 100); + } } mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(item.getClass().getName(item)); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index be55b681f1..2a9ff5e84f 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -371,10 +371,17 @@ namespace MWMechanics return; const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); - if(weaphashealth) + if (weaphashealth) { - int weaphealth = weapon.getClass().getItemHealth(weapon); int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); + + if (weapmaxhealth == 0) + { + damage = 0; + return; + } + + int weaphealth = weapon.getClass().getItemHealth(weapon); damage *= (float(weaphealth) / weapmaxhealth); } From 54bd7b2dcff287451c2eea0b318b098ad5e90a35 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 25 Oct 2018 15:45:31 +0300 Subject: [PATCH 17/31] Implement getItemNormalizedHealth() method and use it --- apps/openmw/mwclass/npc.cpp | 11 +---------- apps/openmw/mwgui/tradewindow.cpp | 13 ++----------- apps/openmw/mwgui/windowmanagerimp.cpp | 12 ++---------- apps/openmw/mwmechanics/combat.cpp | 11 +---------- apps/openmw/mwworld/class.cpp | 12 ++++++++++++ apps/openmw/mwworld/class.hpp | 3 +++ 6 files changed, 21 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index f7172ac0b9..b8b1e9600d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1148,16 +1148,7 @@ namespace MWClass const bool hasHealth = it->getClass().hasItemHealth(*it); if (hasHealth) { - int armorMaxHealth = it->getClass().getItemMaxHealth(*it); - if (armorMaxHealth == 0) - { - ratings[i] = 0; - } - else - { - int armorHealth = it->getClass().getItemHealth(*it); - ratings[i] *= (float(armorHealth) / armorMaxHealth); - } + ratings[i] *= it->getClass().getItemNormalizedHealth(*it); } } } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 2fd91fd4a0..ce8193da25 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -34,17 +34,8 @@ namespace { float price = static_cast(item.getClass().getValue(item)); if (item.getClass().hasItemHealth(item)) - { - if (item.getClass().getItemMaxHealth(item) == 0) - { - price = 0; - } - else - { - price *= item.getClass().getItemHealth(item); - price /= item.getClass().getItemMaxHealth(item); - } - } + price *= item.getClass().getItemNormalizedHealth(item); + return static_cast(price * count); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 3b26edecb8..27ea534a74 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1400,17 +1400,9 @@ namespace MWGui int durabilityPercent = 100; if (item.getClass().hasItemHealth(item)) { - int weapmaxhealth = item.getClass().getItemMaxHealth(item); - if (weapmaxhealth == 0) - { - durabilityPercent = 0; - } - else - { - int weaphealth = item.getClass().getItemHealth(item); - durabilityPercent = static_cast(weaphealth / static_cast(weapmaxhealth) * 100); - } + durabilityPercent = static_cast(item.getClass().getItemNormalizedHealth(item)); } + mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(item.getClass().getName(item)); } diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 2a9ff5e84f..41e2485ce0 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -373,16 +373,7 @@ namespace MWMechanics const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); if (weaphashealth) { - int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); - - if (weapmaxhealth == 0) - { - damage = 0; - return; - } - - int weaphealth = weapon.getClass().getItemHealth(weapon); - damage *= (float(weaphealth) / weapmaxhealth); + damage *= weapon.getClass().getItemNormalizedHealth(weapon); } static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get() diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 68d5998ac9..70fde33cfb 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -83,6 +83,18 @@ namespace MWWorld return ptr.getCellRef().getCharge(); } + float Class::getItemNormalizedHealth (const ConstPtr& ptr) const + { + if (getItemMaxHealth(ptr) == 0) + { + return 0.f; + } + else + { + return getItemHealth(ptr) / static_cast(getItemMaxHealth(ptr)); + } + } + int Class::getItemMaxHealth (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have item health"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 132b7e9e87..af88b0dcca 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -112,6 +112,9 @@ namespace MWWorld virtual int getItemHealth (const ConstPtr& ptr) const; ///< Return current item health or throw an exception if class does not have item health + virtual float getItemNormalizedHealth (const ConstPtr& ptr) const; + ///< Return current item health re-scaled to maximum health + virtual int getItemMaxHealth (const ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exception) From c3e8d536cdbfedd8dccdfac24b9fd10299376f1b Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 25 Oct 2018 16:09:07 +0300 Subject: [PATCH 18/31] Implement getNormalizedEnchantmentCharge() method and use it --- apps/openmw/mwgui/sortfilteritemmodel.cpp | 46 +---------------------- apps/openmw/mwgui/tradewindow.cpp | 3 +- apps/openmw/mwgui/windowmanagerimp.cpp | 18 +-------- apps/openmw/mwworld/cellref.cpp | 16 ++++++++ apps/openmw/mwworld/cellref.hpp | 3 ++ 5 files changed, 24 insertions(+), 62 deletions(-) diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 5172c85ee8..23f8a121b3 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -86,66 +86,24 @@ namespace if (!leftName.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(leftName); - if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) - { leftChargePercent = 101; - } else - { - int maxEnchCharge = ench->mData.mCharge; - if (maxEnchCharge == 0) - { - leftChargePercent = 0; - } - else - { - float enchCharge = left.mBase.getCellRef().getEnchantmentCharge(); - if (enchCharge == -1) - { - leftChargePercent = 100; - } - else - { - leftChargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); - } - } - } + leftChargePercent = static_cast(left.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); } } if (!rightName.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(rightName); - if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) - { rightChargePercent = 101; - } else - { - int maxEnchCharge = ench->mData.mCharge; - if (maxEnchCharge == 0) - { - rightChargePercent = 0; - } - else - { - float enchCharge = right.mBase.getCellRef().getEnchantmentCharge(); - if (enchCharge == -1) - { - rightChargePercent = 100; - } - else - { - rightChargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); - } - } - } + rightChargePercent = static_cast(right.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); } } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index ce8193da25..90698dfc4f 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -34,8 +34,9 @@ namespace { float price = static_cast(item.getClass().getValue(item)); if (item.getClass().hasItemHealth(item)) + { price *= item.getClass().getItemNormalizedHealth(item); - + } return static_cast(price * count); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 27ea534a74..5ffed48156 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1369,22 +1369,7 @@ namespace MWGui const ESM::Enchantment* ench = mStore->get() .find(item.getClass().getEnchantment(item)); - int chargePercent = 100; - - int maxEnchCharge = ench->mData.mCharge; - if (maxEnchCharge == 0) - { - chargePercent = 0; - } - else - { - float enchCharge = item.getCellRef().getEnchantmentCharge(); - if (enchCharge != -1) - { - chargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); - } - } - + int chargePercent = static_cast(item.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); mHud->setSelectedEnchantItem(item, chargePercent); mSpellWindow->setTitle(item.getClass().getName(item)); } @@ -1402,7 +1387,6 @@ namespace MWGui { durabilityPercent = static_cast(item.getClass().getItemNormalizedHealth(item)); } - mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(item.getClass().getName(item)); } diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 72ee56e6a1..094669bddf 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -70,6 +70,22 @@ namespace MWWorld return mCellRef.mEnchantmentCharge; } + float CellRef::getNormalizedEnchantmentCharge(int maxCharge) const + { + if (maxCharge == 0) + { + return 0; + } + else if (mCellRef.mEnchantmentCharge == -1) + { + return 1; + } + else + { + return mCellRef.mEnchantmentCharge / static_cast(maxCharge); + } + } + void CellRef::setEnchantmentCharge(float charge) { if (charge != mCellRef.mEnchantmentCharge) diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 7e27e6ef33..5646bafb04 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -56,6 +56,9 @@ namespace MWWorld // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). float getEnchantmentCharge() const; + // Remaining enchantment charge rescaled to the supplied maximum charge (such as one of the enchantment). + float getNormalizedEnchantmentCharge(int maxCharge) const; + void setEnchantmentCharge(float charge); // For weapon or armor, this is the remaining item health. From e7de6b974aac469adfa9bb23956ac961104133bf Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 21 Sep 2018 16:34:23 +0400 Subject: [PATCH 19/31] Optimize actors processing 1. Do not update physics and animations for actors outside processing range (bug #4647) 2. Do not render such actors 3. Add transparency to actors near processing border, so they will not pop up suddenly --- apps/openmw/mwbase/mechanicsmanager.hpp | 4 + apps/openmw/mwbase/world.hpp | 3 + apps/openmw/mwgui/settingswindow.cpp | 2 + apps/openmw/mwmechanics/actors.cpp | 97 ++++++++++++------- apps/openmw/mwmechanics/actors.hpp | 4 + apps/openmw/mwmechanics/aipackage.cpp | 4 +- apps/openmw/mwmechanics/aitravel.cpp | 6 +- apps/openmw/mwmechanics/character.cpp | 52 ++++++---- apps/openmw/mwmechanics/character.hpp | 3 +- .../mwmechanics/mechanicsmanagerimp.cpp | 19 ++++ .../mwmechanics/mechanicsmanagerimp.hpp | 6 ++ apps/openmw/mwphysics/physicssystem.cpp | 27 ++++++ apps/openmw/mwphysics/physicssystem.hpp | 2 + apps/openmw/mwrender/animation.cpp | 32 +++--- apps/openmw/mwworld/worldimp.cpp | 16 +++ apps/openmw/mwworld/worldimp.hpp | 3 + .../reference/modding/settings/game.rst | 15 ++- files/mygui/openmw_settings_window.layout | 33 ++++++- files/settings-default.cfg | 3 + 19 files changed, 258 insertions(+), 73 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 8137bad959..0566282118 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -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 >& 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; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 027d1fd102..864b811583 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -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. diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 80ed9202a0..6e6924f28e 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -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) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2a68591a8f..d4350e07c9 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -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 actors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, aiProcessingDistance, actors); + getObjectsInRange(position, mActorsProcessingRange, actors); for(std::vector::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 list; std::vector 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 list; std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, aiProcessingDistance, neighbors); + getObjectsInRange(position, mActorsProcessingRange, neighbors); std::set followers; getActorsFollowing(actor, followers); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index a1e0e511da..61879c432f 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -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; }; } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 9a42f191ef..7045d28e54 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -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; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 8b52b15a4b..90beb9ead5 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -3,8 +3,9 @@ #include #include -#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; } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ce844674f4..d5e22cb070 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -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; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index f97614ef45..0f4b3aa90a 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -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); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 619ed91b3e..1c75763167 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -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); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 3682e97cec..26eaa968d8 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H #define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H +#include + #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); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2ce498f377..9ee47f3f5c 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -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(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 3ef9990f53..2de869395d 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -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(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6e2c76d1d8..81701797d7 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1748,21 +1748,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(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 { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0f20fa05a1..3b1d84a66b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1582,6 +1582,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()) @@ -3559,7 +3569,13 @@ namespace MWWorld MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( origin, feetToGameUnits(static_cast(effectIt->mArea)), objects); for (std::vector::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 diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1592453a20..9b027ec5a6 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -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. diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 4c3f4579f7..254496d5b6 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -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 diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 8ff850cae5..8e6e98612b 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -73,7 +73,32 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -83,7 +108,7 @@ - + @@ -93,7 +118,7 @@ - + @@ -103,7 +128,7 @@ - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ea6f6e7b66..66c6c65774 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -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 From bf9e8c4556e3f545239dfc3c061b8b8baaa98aef Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 26 Oct 2018 12:36:58 +0400 Subject: [PATCH 20/31] Make spell absorption multiplicative (bug #4684) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/spellcasting.cpp | 40 ++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07c0217da6..9fa330e778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,7 @@ 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 #4684: Spell Absorption is additive Bug #4685: Missing sound causes an exception inside Say command Bug #4689: Default creature soundgen entries are not used Feature #912: Editor: Add missing icons to UniversalId tables diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index a91aa2f31c..4a74e1647f 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -324,6 +324,34 @@ namespace MWMechanics return true; } + class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor + { + public: + float mProbability; + + GetAbsorptionProbability(const MWWorld::Ptr& actor) + : mProbability(0.f){} + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1) + { + if (key.mId == ESM::MagicEffect::SpellAbsorption) + { + if (mProbability == 0.f) + mProbability = magnitude / 100; + else + { + // If there are different sources of SpellAbsorption effect, multiply failing probability for all effects. + // Real absorption probability will be the (1 - total fail chance) in this case. + float failProbability = 1.f - mProbability; + failProbability *= 1.f - magnitude / 100; + mProbability = 1.f - failProbability; + } + } + } + }; + CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) : mCaster(caster) , mTarget(target) @@ -444,12 +472,18 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->setEnemy(target); // Try absorbing if it's a spell - // NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, not sure - // if that is worth replicating. + // NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, so use the same approach here bool absorbed = false; if (spell && caster != target && target.getClass().isActor()) { - float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude(); + GetAbsorptionProbability check(target); + MWMechanics::CreatureStats& stats = target.getClass().getCreatureStats(target); + stats.getActiveSpells().visitEffectSources(check); + stats.getSpells().visitEffectSources(check); + if (target.getClass().hasInventoryStore(target)) + target.getClass().getInventoryStore(target).visitEffectSources(check); + + int absorb = check.mProbability * 100; absorbed = (Misc::Rng::roll0to99() < absorb); if (absorbed) { From 19ce1abcdf40d196ae88d242e5b319f03d7a05ab Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 26 Oct 2018 12:53:15 +0300 Subject: [PATCH 21/31] Fix selected weapon HUD durability percentage --- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 5ffed48156..3937518552 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1385,7 +1385,7 @@ namespace MWGui int durabilityPercent = 100; if (item.getClass().hasItemHealth(item)) { - durabilityPercent = static_cast(item.getClass().getItemNormalizedHealth(item)); + durabilityPercent = static_cast(item.getClass().getItemNormalizedHealth(item) * 100); } mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(item.getClass().getName(item)); From f62ca24356a16c7148bef07bdaf2ba19464f2236 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Fri, 26 Oct 2018 19:33:04 +0200 Subject: [PATCH 22/31] [macOS, CI] Use dependencies with downgraded MyGUI, fixing #4665 --- CI/before_install.osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 2ab996b10f..51b4b90be9 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -6,5 +6,5 @@ brew outdated cmake || brew upgrade cmake brew outdated pkgconfig || brew upgrade pkgconfig brew install qt -curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-100d2e0.zip -o ~/openmw-deps.zip +curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-4eec887.zip -o ~/openmw-deps.zip unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null From 4873d33642ca6973a266244e3b73c40da5f6c6a0 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 27 Oct 2018 01:25:16 +0300 Subject: [PATCH 23/31] Adjust magic light source linear attenuation (bug #3890) --- CHANGELOG.md | 1 + apps/openmw/mwrender/animation.cpp | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fa330e778..ed387eaf63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla Bug #3836: Script fails to compile when command argument contains "\n" Bug #3876: Landscape texture painting is misaligned + Bug #3890: Magic light source attenuation is inaccurate Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters Bug #3920: RemoveSpellEffects doesn't remove constant effects diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f165b6bd3d..8ae8800a6c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1789,9 +1789,12 @@ namespace MWRender } else { - effect += 3; - float radius = effect * 66.f; - float linearAttenuation = 0.5f / effect; + // TODO: use global attenuation settings + + // 1 pt of Light effect magnitude corresponds to 1 foot of light source radius, which is about 21.33 game units, + // but Morrowind uses imprecise value of foot for magic effects. + float radius = effect * 22.f; + float linearAttenuation = 3.f / radius; if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation()) { @@ -1813,7 +1816,8 @@ namespace MWRender mGlowLight->setLight(light); } - mGlowLight->setRadius(radius); + // Make the obvious cut-off a bit less obvious + mGlowLight->setRadius(radius * 3); } } From 89f4ad18356353b2c1ddca2a7ef0a671ab624c81 Mon Sep 17 00:00:00 2001 From: Alec Nunn Date: Fri, 26 Oct 2018 21:52:56 -0700 Subject: [PATCH 24/31] Worked with formatting the documentation source to conform to standards desired. Also corrected numerous spelling errors and inconsistencies. --- docs/source/reference/documentationHowTo.rst | 102 +++++++++++++---- docs/source/reference/modding/differences.rst | 26 +++-- docs/source/reference/modding/font.rst | 17 ++- docs/source/reference/modding/foreword.rst | 8 +- docs/source/reference/modding/mod-install.rst | 10 +- .../source/reference/modding/settings/HUD.rst | 3 +- .../reference/modding/settings/cells.rst | 20 +++- .../reference/modding/settings/game.rst | 22 ++-- .../source/reference/modding/settings/map.rst | 7 +- .../reference/modding/settings/saves.rst | 7 +- .../reference/modding/settings/shaders.rst | 5 +- .../reference/modding/settings/sound.rst | 4 +- .../reference/modding/settings/terrain.rst | 3 +- .../reference/modding/settings/video.rst | 6 +- .../reference/modding/settings/windows.rst | 5 +- .../convert-bump-mapped-mods.rst | 106 ++++++++++++++---- .../texture-modding/texture-basics.rst | 10 +- 17 files changed, 273 insertions(+), 88 deletions(-) diff --git a/docs/source/reference/documentationHowTo.rst b/docs/source/reference/documentationHowTo.rst index b27c122cb5..75dbe8dca2 100644 --- a/docs/source/reference/documentationHowTo.rst +++ b/docs/source/reference/documentationHowTo.rst @@ -8,9 +8,19 @@ Or a beginner's guide to writing docs without having to deal with more techie st Intro ===== -The premise of this guide is that you would like to help out the OpenMW project beyond play-testing for bugs and such, buuuuut you're like me and don't really know how to code. This has the rather pesky side effect of you not really knowing about all the tools like GitHub and such. While many of these tools are super handy and great to know how to use, not everyone has the actual need and desire to learn the ins and outs of them. Since we would like as much help fleshing out the user documentation as possible, I wrote this guide to lower the barrier of entry into contributing to the project. +The premise of this guide is that you would like to help out the OpenMW project beyond play-testing for bugs and such, +*buuuuut* you're like me and don't really know how to code. +This has the rather pesky side effect of you not really knowing about all the tools like GitHub and such. +While many of these tools are super handy and great to know how to use, +not everyone has the actual need and desire to learn the ins and outs of them. +Since we would like as much help fleshing out the user documentation as possible, +I wrote this guide to lower the barrier of entry into contributing to the project. -*However*, as much as I will try to guide you through all the tedious setup and day-to-day stuff, you will eventually have to learn to write using ReST (reStructuredText) formatting. Since you're probably like me when I started helping and don't know wtf ReST is, never fear. It's an incredibly simple language that is easy to read in plain text form that can then be converted automatically into different types of documents like PDFs and html for webpages. +*However*, as much as I will try to guide you through all the tedious setup and day-to-day stuff, +you will eventually have to learn to write using ReST (reStructuredText) formatting. +Since you're probably like me when I started helping and don't know wtf ReST is, never fear. +It's an incredibly simple language that is easy to read in plain text form that can then be converted automatically +into different types of documents like PDFs and html for webpages. Baby Steps ========== @@ -25,9 +35,11 @@ Choose Repository and Files in the menu on the left, then docs and source in the Don’t overlook the tutorial-style-guide.txt there for some tips to get you started. Open whichever file you want to tackle – probably within the manuals or reference directories. -There’s also a dropdown box to the right of edit, at the top of the left menu, which offers options such as new file or directory, or upload file, with “+” to close that dropdown box. +There’s also a dropdown box to the right of edit, at the top of the left menu, +which offers options such as new file or directory, or upload file, with “+” to close that dropdown box. -Click on "Edit" towards top right which will reveal the underlying version, rather than the version displayed to normal reaaders. Use "Write" and "Preview" to switch between the two views. +Click on "Edit" towards top right which will reveal the underlying version, +rather than the version displayed to normal readers. Use "Write" and "Preview" to switch between the two views. When you have made the appropriate changes, and checked them in Preview mode, click the Green "Commit changes" button at the bottom. This should add a branch, with a default name such as patch-1, to your own repository, and add a Merge Request to the main OpenMW Project. @@ -48,13 +60,24 @@ So here's what you're gonna be learning how to set up: GitHub ====== -GitHub is the website the OpenMW project is hosted on. It utilizes Git, which is a version control system, meaning it helps us all collaborate on the project without interfering with each others' work. The commands are a little annoying because there is a certain amount of undescriptive jargon, but for the most part, what you need to know is very simple and I'll walk you through it. There are three main parts that you should know: +GitHub is the website the OpenMW project is hosted on. It utilizes Git, which is a version control system, +meaning it helps us all collaborate on the project without interfering with each others' work. +The commands are a little annoying because there is a certain amount of undescriptive jargon, +but for the most part, what you need to know is very simple and I'll walk you through it. +There are three main parts that you should know: 1. The OpenMW repository 2. Your online repository 3. Your local repository -The master OpenMW respository is where all of our work comes together and where the most current version of the source code resides. A repository, also called repo, is a directory or the main folder that holds a project. You will need to create your own account on GitHub so you can *fork* the OpenMW repository. Forking is just when you clone a project into a repository on your own account so you can make changes however you like without accidentally messing up the original project. Now, you could add and edit files on GitHub.com directly through your online repository, however it's much easier to work on them on your own computer in your local repository. Local just refers to the fact that it's physically stored on your computer's hard drive. Here are the easy steps for doing all this: +The master OpenMW repository is where all of our work comes together and where the most current version of the source code resides. +A repository, also called repo, is a directory or the main folder that holds a project. +You will need to create your own account on GitHub so you can *fork* the OpenMW repository. +Forking is just when you clone a project into a repository on your own account so you can make changes however you like +without accidentally messing up the original project. +Now, you could add and edit files on GitHub.com directly through your online repository, +however it's much easier to work on them on your own computer in your local repository. +Local just refers to the fact that it's physically stored on your computer's hard drive. Here are the easy steps for doing all this: 1. Go to GitHub.com and sign up for a free account. 2. Navigate to the master OpenMW repo at: https://github.com/OpenMW/openmw @@ -67,7 +90,11 @@ If you want more info I recommend reading this guide: https://readwrite.com/2013 PyCharm ======= -PyCharm is what's known as an IDE, which stands for integrated development environment. All this means is that it's for writing code and has a bunch of built-in features that make it easier to do so. In this case, PyCharm is made for the language Python, which is what Sphinx is written in. We won't actually be touching any of the Python, but some of the built-in features are extremely useful. Let's start setting it up: +PyCharm is what's known as an IDE, which stands for integrated development environment. +All this means is that it's for writing code and has a bunch of built-in features that make it easier to do so. +In this case, PyCharm is made for the language Python, which is what Sphinx is written in. +We won't actually be touching any of the Python, but some of the built-in features are extremely useful. +Let's start setting it up: 1. Go to https://www.jetbrains.com/pycharm/download/ 2. Select your OS, then download the free Community version. @@ -81,31 +108,61 @@ PyCharm is what's known as an IDE, which stands for integrated development envir 10. Back in the welcome window, click "Check out from version control" and select GitHub. .. note:: - After this step, it should log in to your GitHub. If not, you probably messed up the Token creation. If you're on Mac, you may come across and error complaining about XCode and admin priviledges. If this happens, open Terminal and type: ``sudo xcodebuild -license`` Read through the license and agree. This should fix the error and allow you to log in. + After this step, it should log in to your GitHub. If not, you probably messed up the Token creation. + If you're on Mac, you may come across and error complaining about XCode and admin priviledges. If this happens, + open Terminal and type: ``sudo xcodebuild -license`` Read through the license and agree. + This should fix the error and allow you to log in. 11. In Git Repository URL, select your OpenMW repository and click Clone -Congrats! You now have the OpenMW sourcecode on your computer and you can begin making changes and contributing. If you're reading this guide though, you probably won't have any idea how to do that, so let's go through setting up Sphinx, then I'll go through it. +Congrats! You now have the OpenMW source code on your computer and you can begin making changes and contributing. +If you're reading this guide though, you probably won't have any idea how to do that, +so let's go through setting up Sphinx, then I'll go through it. Sphinx ====== -So far I've mentioned ReST (reStructuredText) a couple times, but what is it, and what is Sphinx? The most basic explanation is that ReST is the markup language (like HTML is the markup language for webpages) and Sphinx is the program that goes through and builds the actual document so you can read it in a more visually pleasing way. For a much more detailed explanation, I recommend: https://coderwall.com/p/vemncg/what-is-the-difference-rest-docutils-sphinx-readthedocs +So far I've mentioned ReST (reStructuredText) a couple times, but what is it, and what is Sphinx? +The most basic explanation is that ReST is the markup language (like HTML is the markup language for webpages) +and Sphinx is the program that goes through and builds the actual document so you can read it in a more visually pleasing way. +For a much more detailed explanation, I recommend: https://coderwall.com/p/vemncg/what-is-the-difference-rest-docutils-sphinx-readthedocs -This will be the most technical section as we have to use the command prompt or terminal to install Python and Sphinx. I had intended to give you a universal explanation on how to install both, but it would drastically increase the length of this guide. The tutorial on the Sphinx website is really just going to be better than anything I write here, so please refer to their guide here: https://www.sphinx-doc.org/en/stable/install.html +This will be the most technical section as we have to use the command prompt or terminal to install Python and Sphinx. +I had intended to give you a universal explanation on how to install both, +but it would drastically increase the length of this guide. +The tutorial on the Sphinx website is really just going to be better than anything I write here, +so please refer to their guide here: https://www.sphinx-doc.org/en/stable/install.html Hopefully you now have Python and Sphinx installed. ... -Now you should have everything installed and running so you can collaborate on documentation properly. Let's go through a few more brief GitHub basics. There are really only 4 things you will be using regularly: +Now you should have everything installed and running so you can collaborate on documentation properly. +Let's go through a few more brief GitHub basics. There are really only 4 things you will be using regularly: 1. Rebase 2. Commit 3. Push 4. Pull request (PR) -Rebasing means you're taking all changes in one branch and applying them directly on top of another branch. This is slightly different than a merge which compares the two branches and makes another state combining the two. The difference is slight, but we use the rebase because it keeps the history cleaner. You will always rebase your local repository from the OpenMW master repository. This ensures you have all the most up to date changes before working on stuff so there is less chance of conflicts that need to be resolved when your branch is merged back into the master. A commit is basically just stating which files you want to mark as ready to be "pushed" to your online repository. A push is just copying those "committed" changes to your online repo. (Commit and push can be combined in one step in PyCharm, so yay) Once you've pushed all the changes you need to contribute something to the project, you will then submit a pull request, so called because you are *requesting* that the project maintainers "pull" and merge the changes you've made into the project master repository. One of the project maintainers will probably ask you to make some corrections or clarifications. Go back and repeat this process to make those changes, and repeat until they're good enough to get merged. +Rebasing means you're taking all changes in one branch and applying them directly on top of another branch. +This is slightly different than a merge which compares the two branches and makes another state combining the two. +The difference is slight, but we use the rebase because it keeps the history cleaner. +You will always rebase your local repository from the OpenMW master repository. +This ensures you have all the most up to date changes before working on stuff so there is less chance of conflicts that +need to be resolved when your branch is merged back into the master. +A commit is basically just stating which files you want to mark as ready to be "pushed" to your online repository. +A push is just copying those "committed" changes to your online repo. +(Commit and push can be combined in one step in PyCharm, so yay) +Once you've pushed all the changes you need to contribute something to the project, you will then submit a pull request, +so called because you are *requesting* that the project maintainers "pull" + and merge the changes you've made into the project master repository. One of the project maintainers will probably ask + you to make some corrections or clarifications. Go back and repeat this process to make those changes, + and repeat until they're good enough to get merged. -So to go over all that again. You rebase *every* time you start working on something to ensure you're working on the most updated version (I do literally every time I open PyCharm). Then make your edits. You commit and push from your local repo to your online repo. Then you submit a pull request and people can review your changes before they get merged into the project master! Or in list form: +So to go over all that again. You rebase *every* time you start working on something to ensure you're working on the most +updated version (I do literally every time I open PyCharm). Then make your edits. +You commit and push from your local repo to your online repo. +Then you submit a pull request and people can review your changes before they get merged into the project master! +Or in list form: 1. Rebase local repo from OpenMW master 2. Make your edits @@ -116,7 +173,9 @@ So to go over all that again. You rebase *every* time you start working on somet Preview Documentation ********************* -You will probably find it helpful to be able to preview any documentation you've made. I often forget necessary syntax and this allows me to double check my work before submitting a PR. Luckily, PyCharm has a handy built-in feature that allows you to easily generate the docs. +You will probably find it helpful to be able to preview any documentation you've made. +I often forget necessary syntax and this allows me to double check my work before submitting a PR. +Luckily, PyCharm has a handy built-in feature that allows you to easily generate the docs. 1. In the top right corner of the PyCharm window, select the drop-down menu and select `Edit Configurations`. 2. In the `Run/Debug Configurations` dialogue, click the green plus button in the top left and select `Python Docs > Sphinx Tasks`. @@ -127,16 +186,21 @@ You will probably find it helpful to be able to preview any documentation you've :Output: 4. Click `Apply`, then `OK`. -Now in order to generate the documentation on your computer to preview them, just click the green play button in the top right, next to the drop down menu with the name you chose above selected. Sphinx will run and you can view the resulting documentation wherever you chose Output to be, above. The window that Sphinx runs in will also show any errors that occur during the build in red, which should help you find typos and missing/incorrect syntax. +Now in order to generate the documentation on your computer to preview them, +just click the green play button in the top right, next to the drop down menu with the name you chose above selected. +Sphinx will run and you can view the resulting documentation wherever you chose Output to be, above. +The window that Sphinx runs in will also show any errors that occur during the build in red, +which should help you find typos and missing/incorrect syntax. GitLab integration in PyCharm ============================= -As most of the hosting of OpenMW has moved to Gitlab, we should encourage the use of GitLab, though GitHub will continue to be supported. +As most of the hosting of OpenMW has moved to Gitlab, we should encourage the use of GitLab, +though GitHub will continue to be supported. -Add a couple of plugins to Pycharm - see general instructions at https://www.jetbrains.com/help/pycharm/installing-updating-and-uninstalling-repository-plugins.html +Add a couple of plugins to PyCharm - see general instructions at https://www.jetbrains.com/help/pycharm/installing-updating-and-uninstalling-repository-plugins.html -For Linux/Windows - (Macos is a little different) +For Linux/Windows - (MacOS is a little different) 1. File/Settings/Plugins 2. Browse Repositories diff --git a/docs/source/reference/modding/differences.rst b/docs/source/reference/modding/differences.rst index d492dc542a..5533a3aa37 100644 --- a/docs/source/reference/modding/differences.rst +++ b/docs/source/reference/modding/differences.rst @@ -4,15 +4,19 @@ Modding OpenMW vs Morrowind A brief overview of the differences between the two engines. ============================================================ -OpenMW is designed to be able to use all the normal Morrowind mod files such as ESM/ESP plugins, texture replacers, mesh replacers, etc. +OpenMW is designed to be able to use all the normal Morrowind mod files such as ESM/ESP plugins, texture replacers, +mesh replacers, etc. .. warning:: - All external programs and libraries that depend on ``morrowind.exe`` cannot function with OpenMW. This means you should assume mods dependent on Morrowind Graphics Extender, Morrowind Code Patch, Morrowind Script Extender, etc, will *not* work correctly, nor will the tools themselves. + All external programs and libraries that depend on ``morrowind.exe`` cannot function with OpenMW. + This means you should assume mods dependent on Morrowind Graphics Extender, Morrowind Code Patch, + Morrowind Script Extender, etc, will *not* work correctly, nor will the tools themselves. Multiple Data Folders --------------------- -The largest difference between OpenMW and Morrowind in terms of data structure is OpenMW's support of multiple data folders. This has many advantages, especially when it comes to uninstalling mods and preventing unintentional overwrites of files. +The largest difference between OpenMW and Morrowind in terms of data structure is OpenMW's support of multiple data folders. +This has many advantages, especially when it comes to uninstalling mods and preventing unintentional overwrites of files. .. warning:: Most mods can still be installed into the root OpenMW data folder, but this is not recommended. @@ -34,25 +38,33 @@ To uninstall these mods simply delete that mod's respective ``data=`` entry. The mods are loaded in the order of these entries, with the top being overwritten by mods added towards the bottom. .. note:: - Mods that depend on ESM/ESP plugins can be rearranged within the OpenMW Launcher, but mesh/texture replacer mods can only be reordered by moving their ``data=`` entry. + Mods that depend on ESM/ESP plugins can be rearranged within the OpenMW Launcher, + but mesh/texture replacer mods can only be reordered by moving their ``data=`` entry. OpenMW Launcher --------------- -The launcher included with OpenMW is similar to the original Morrowind Launcher. Go to the Data Files tab to enable and disable plugins. You can also drag list items to modify the load order. Content lists can be created at the bottom by clicking the New Content List button, creating a list name, then setting up a new modlist. This is helpful for different player profiles and testing out different load orders. +The launcher included with OpenMW is similar to the original Morrowind Launcher. +Go to the Data Files tab to enable and disable plugins. You can also drag list items to modify the load order. +Content lists can be created at the bottom by clicking the New Content List button, creating a list name, +then setting up a new modlist. This is helpful for different player profiles and testing out different load orders. .. TODO use a substitution image for the New Content List button. Settings.cfg ------------ -The ``settings.cfg`` file is essentially the same as the INI files for Morrowind. It is located in the same directory as ``openmw.cfg``. This is where many video, audio, GUI, input, etc. settings can be modified. Some are available in-game, but many are only available in this configuration file. Please see https://wiki.openmw.org/index.php?title=Settings for the complete listing. +The ``settings.cfg`` file is essentially the same as the INI files for Morrowind. +It is located in the same directory as ``openmw.cfg``. This is where many video, audio, GUI, input, etc. +settings can be modified. Some are available in-game, but many are only available in this configuration file. +Please see https://wiki.openmw.org/index.php?title=Settings for the complete listing. .. TODO Create a proper ReST document tree for all the settings rather than Wiki. Open Source Resources Support ----------------------------- -While OpenMW supports all of the original files that Morrowind supported, we've expanded support to many open source file formats. These are summarized below: +While OpenMW supports all of the original files that Morrowind supported, +we've expanded support to many open source file formats. These are summarized below: diff --git a/docs/source/reference/modding/font.rst b/docs/source/reference/modding/font.rst index 80d01c27f7..213cdf7600 100644 --- a/docs/source/reference/modding/font.rst +++ b/docs/source/reference/modding/font.rst @@ -4,14 +4,20 @@ Fonts Morrowind .fnt fonts -------------------- -Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format, nor compatible with ``.fnt`` formats from any other Bethesda games. To our knowledge, the format is undocumented and no tools for viewing or editing these fonts exist. +Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format, +nor compatible with ``.fnt`` formats from any other Bethesda games. To our knowledge, +the format is undocumented and no tools for viewing or editing these fonts exist. -OpenMW can load this format and convert it on the fly into something usable (see font loader `source code `_). In OpenMW 0.32, an --export-fonts command line option was added to write the converted font (a PNG image and an XML file describing the position of each glyph in the image) to the current directory. +OpenMW can load this format and convert it on the fly into something usable +(see font loader `source code `_). +In OpenMW 0.32, an --export-fonts command line option was added to write the converted font +(a PNG image and an XML file describing the position of each glyph in the image) to the current directory. TrueType fonts -------------- -Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. This is the recommended way to create new fonts. +Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. +This is the recommended way to create new fonts. - To replace the primary "Magic Cards" font: @@ -74,4 +80,7 @@ Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. Th Bitmap fonts ------------ -Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because of no Unicode support. MyGUI has its own format for bitmap fonts. An example can be seen by using the --export-fonts command line option (see above), which converts Morrowind ``.fnt`` to a MyGUI bitmap font. This is the recommended format to use if you wish to edit Morrowind's bitmap font or create a new bitmap font. +Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because of no Unicode support. +MyGUI has its own format for bitmap fonts. An example can be seen by using the --export-fonts command line option (see above), +which converts Morrowind ``.fnt`` to a MyGUI bitmap font. +This is the recommended format to use if you wish to edit Morrowind's bitmap font or create a new bitmap font. diff --git a/docs/source/reference/modding/foreword.rst b/docs/source/reference/modding/foreword.rst index cf72b2aa36..30b5fdb3be 100644 --- a/docs/source/reference/modding/foreword.rst +++ b/docs/source/reference/modding/foreword.rst @@ -1,4 +1,10 @@ Foreword ######## -OpenMW is a complete game engine built to be content agnostic. The majority of this guide is applicable to any non-Morrowind project using its engine. That being said, it was designed with the extensive modding community of Morrowind in mind. Therefore, if you are already familiar with modding in Morrowind, you will likely be able to start modding in OpenMW with little to no instruction. We do recommend you at least refer to :doc:`differences` to find out about what's different between OpenMW and the original Morrowind engine. For everyone else, or just a good refresher, read on! \ No newline at end of file +OpenMW is a complete game engine built to be content agnostic. +The majority of this guide is applicable to any non-Morrowind project using its engine. +That being said, it was designed with the extensive modding community of Morrowind in mind. +Therefore, if you are already familiar with modding in Morrowind, +you will likely be able to start modding in OpenMW with little to no instruction. +We do recommend you at least refer to :doc:`differences` to find out about what's different between OpenMW and the +original Morrowind engine. For everyone else, or just a good refresher, read on! \ No newline at end of file diff --git a/docs/source/reference/modding/mod-install.rst b/docs/source/reference/modding/mod-install.rst index e62c27fc18..2c883aa599 100644 --- a/docs/source/reference/modding/mod-install.rst +++ b/docs/source/reference/modding/mod-install.rst @@ -21,16 +21,20 @@ Install #. If your mod contains resources in a ``.bsa`` file, go to near the top of the file, locate the entries like ''fallback-archive=Morrowind.bsa'' and create a new line underneath and type: ``fallback-archive=.bsa''``. .. note:: - Some text editors, such as TextEdit on Mac, will autocorrect your double quotes to typographical "curly" quotes instead of leaving them as the proper neutral vertical quotes ``""``. + Some text editors, such as TextEdit on Mac, will auto-correct your double quotes to typographical "curly" + quotes instead of leaving them as the proper neutral vertical quotes ``""``. #. Save your ``openmw.cfg`` file. -You have now installed your mod. Any simple replacer mods that only contain resource files such as meshes or textures will now automatically be loaded in the order of their ``data=*`` entry. This is important to note because replacer mods that replace the same resource will overwrite previous ones as you go down the list. +You have now installed your mod. Any simple replacer mods that only contain resource files such as meshes or +textures will now automatically be loaded in the order of their ``data=*`` entry. +This is important to note because replacer mods that replace the same resource will overwrite previous ones as you go down the list. Enable ------ -Any mods that have plugin files must be enabled to work. Master game files and plugin files can only be enabled if they have been properly installed within a *data folder* as described above. +Any mods that have plugin files must be enabled to work. +Master game files and plugin files can only be enabled if they have been properly installed within a *data folder* as described above. #. Open the OpenMW Launcher. #. Click on the Data Files tab. diff --git a/docs/source/reference/modding/settings/HUD.rst b/docs/source/reference/modding/settings/HUD.rst index 83811420f7..07a3135c8d 100644 --- a/docs/source/reference/modding/settings/HUD.rst +++ b/docs/source/reference/modding/settings/HUD.rst @@ -8,7 +8,8 @@ crosshair :Range: True/False :Default: True -This setting determines whether the crosshair or reticle is displayed. Enabling the crosshair provides more immediate feedback about which object is currently the focus of actions. +This setting determines whether the crosshair or reticle is displayed. +Enabling the crosshair provides more immediate feedback about which object is currently the focus of actions. Some players perceive that disabling the crosshair provides a more immersive experience. Another common use is to disable the crosshair for screen shots. diff --git a/docs/source/reference/modding/settings/cells.rst b/docs/source/reference/modding/settings/cells.rst index 43a984ca7c..43e51eb7a4 100644 --- a/docs/source/reference/modding/settings/cells.rst +++ b/docs/source/reference/modding/settings/cells.rst @@ -11,7 +11,10 @@ exterior cell load distance This setting determines the number of exterior cells adjacent to the character that will be loaded for rendering. .. Warning:: - Values greater than 1 will significantly affect the frame rate and loading times. This setting is mainly intended for making screenshots of scenic vistas and not for real-time gameplay. Loading more cells can break certain scripts or quests in the game that expect cells to not be loaded until the player is there. These limitations will be addressed in a future version with a separate technique for rendering distant cells. + Values greater than 1 will significantly affect the frame rate and loading times. + This setting is mainly intended for making screenshots of scenic vistas and not for real-time gameplay. + Loading more cells can break certain scripts or quests in the game that expect cells to not be loaded until the player is there. + These limitations will be addressed in a future version with a separate technique for rendering distant cells. This setting interacts with viewing distance and field of view settings. @@ -163,11 +166,13 @@ prediction time :Range: >=0 :Default: 1 -The amount of time (in seconds) in the future to predict the player position for. This predicted position is used to preload any cells and/or distant terrain required at that position. +The amount of time (in seconds) in the future to predict the player position for. +This predicted position is used to preload any cells and/or distant terrain required at that position. This setting will only have an effect if 'preload enabled' is set or the 'distant terrain' in the Terrain section is set. -Increasing this setting from its default may help if your computer/hard disk is too slow to preload in time and you see loading screens and/or lag spikes. +Increasing this setting from its default may help if your computer/hard disk is too slow to preload in time and you see +loading screens and/or lag spikes. cache expiry delay ------------------ @@ -185,7 +190,10 @@ target framerate :Range: >0 :Default: 60 -Affects the time to be set aside each frame for graphics preloading operations. The game will distribute the preloading over several frames so as to not go under the specified framerate. For best results, set this value to the monitor's refresh rate. If you still experience stutters on turning around, you can try a lower value, although the framerate during loading will suffer a bit in that case. +Affects the time to be set aside each frame for graphics preloading operations. +The game will distribute the preloading over several frames so as to not go under the specified framerate. +For best results, set this value to the monitor's refresh rate. If you still experience stutters on turning around, +you can try a lower value, although the framerate during loading will suffer a bit in that case. pointers cache size ------------------- @@ -194,4 +202,6 @@ pointers cache size :Range: >0 :Default: 40 -The count of object pointers that will be saved for a faster search by object ID. This is a temporary setting that can be used to mitigate scripting performance issues with certain game files. If your profiler (press F3 twice) displays a large overhead for the Scripting section, try increasing this setting. +The count of object pointers that will be saved for a faster search by object ID. +This is a temporary setting that can be used to mitigate scripting performance issues with certain game files. +If your profiler (press F3 twice) displays a large overhead for the Scripting section, try increasing this setting. diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 4c3f4579f7..475a2f175c 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -9,11 +9,11 @@ show owned :Default: 0 Enable visual clues for items owned by NPCs when the crosshair is on the object. -If the setting is 0, no clues are provided which is the default Morrowind behavior. +If the setting is 0, no clues are provided which is the default Morrowind behaviour. If the setting is 1, the background of the tool tip for the object is highlighted -in the color specified by the color background owned setting in the GUI Settings Section. -If the setting is 2, the crosshair is the color of the color crosshair owned setting in the GUI Settings section. -If the setting is 3, both the tool tip background and the crosshair are colored. +in the colour specified by the colour background owned setting in the GUI Settings Section. +If the setting is 2, the crosshair is the colour of the colour crosshair owned setting in the GUI Settings section. +If the setting is 3, both the tool tip background and the crosshair are coloured. The crosshair is not visible if crosshair is false. This setting can be configured in Advanced tab of the launcher. @@ -71,12 +71,14 @@ can loot during death animation :Range: True/False :Default: True -If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. -However disposing corpses during death animation is not recommended - death counter may not be incremented, and this behaviour can break quests. +If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, +if they are not in combat. However disposing corpses during death animation is not recommended - +death counter may not be incremented, and this behaviour can break quests. This is how Morrowind behaves. If this setting is false, player has to wait until end of death animation in all cases. -This case is more safe, but makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. +This case is more safe, but makes using of summoned creatures exploit +(looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation. This setting can be toggled in Advanced tab of the launcher. @@ -167,7 +169,8 @@ use additional anim sources :Default: False Allow to load additional animation sources when enabled. -For example, if the main animation mesh has name Meshes/x.nif, an engine will load all KF-files from Animations/x folder and its child folders. +For example, if the main animation mesh has name Meshes/x.nif, +an engine will load all KF-files from Animations/x folder and its child folders. Can be useful if you want to use several animation replacers without merging them. Attention: animations from AnimKit have own format and are not supposed to be directly loaded in-game! This setting can only be configured by editing the settings configuration file. @@ -179,7 +182,8 @@ barter disposition change is permanent :Range: True/False :Default: False -If this setting is true, disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. +If this setting is true, +disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. This imitates the option Morrowind Code Patch offers. This setting can be toggled in Advanced tab of the launcher. diff --git a/docs/source/reference/modding/settings/map.rst b/docs/source/reference/modding/settings/map.rst index c973c26e50..2cf53f704f 100644 --- a/docs/source/reference/modding/settings/map.rst +++ b/docs/source/reference/modding/settings/map.rst @@ -8,7 +8,8 @@ global :Range: True/False :Default: False -If this value is true, the map window will display the world map, otherwise the local map. The setting updates automatically when pressing the local/world map switch button on the map window. +If this value is true, the map window will display the world map, otherwise the local map. +The setting updates automatically when pressing the local/world map switch button on the map window. global map cell size -------------------- @@ -110,4 +111,6 @@ local map cell distance :Range: >= 1 :Default: 1 -Similar to "exterior cell load distance" in the Cells section, controls how many cells are rendered on the local map. Values higher than the default may result in longer loading times. Please note that only loaded cells can be rendered, so this setting must be lower or equal to "exterior cell load distance" to work properly. +Similar to "exterior cell load distance" in the Cells section, controls how many cells are rendered on the local map. +Values higher than the default may result in longer loading times. Please note that only loaded cells can be rendered, +so this setting must be lower or equal to "exterior cell load distance" to work properly. diff --git a/docs/source/reference/modding/settings/saves.rst b/docs/source/reference/modding/settings/saves.rst index 5add36c0c7..0e58d66d84 100644 --- a/docs/source/reference/modding/settings/saves.rst +++ b/docs/source/reference/modding/settings/saves.rst @@ -29,7 +29,8 @@ timeplayed :Default: False This setting determines whether the amount of the time the player has spent playing will be displayed -for each saved game in the Load menu. Currently, the counter includes time spent in menus, including the pause menu, but does not include time spent with the game window minimized. +for each saved game in the Load menu. Currently, the counter includes time spent in menus, including the pause menu, +but does not include time spent with the game window minimized. This setting can only be configured by editing the settings configuration file. @@ -40,6 +41,8 @@ max quicksaves :Range: >0 :Default: 1 -This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, the oldest quicksave will be recycled the next time you perform a quicksave. +This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, +quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, +the oldest quicksave will be recycled the next time you perform a quicksave. This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index be1ecebf0c..b36f642852 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -39,7 +39,8 @@ Only affects objects that render with shaders (see 'force shaders' option). Always affects terrain. Leaving this option at its default makes the lighting compatible with Morrowind's fixed-function method, -but the lighting may appear dull and there might be color shifts. Setting this option to 'false' results in more dynamic lighting. +but the lighting may appear dull and there might be colour shifts. +Setting this option to 'false' results in more dynamic lighting. auto use object normal maps --------------------------- @@ -83,7 +84,7 @@ auto use terrain specular maps :Default: False If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. -The texture must contain the layer color in the RGB channel (as usual), and a specular multiplier in the alpha channel. +The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel. normal map pattern ------------------ diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index b9587a5c46..895b919fb1 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -67,7 +67,7 @@ voice volume :Range: 0.0 (silent) to 1.0 (maximum volume) :Default: 0.8 -This setting controls the volume for spoken dialog from NPCs. +This setting controls the volume for spoken dialogue from NPCs. This setting can be changed in game using the Voice slider from the Audio panel of the Options menu. @@ -122,7 +122,7 @@ hrtf This setting specifies which HRTF profile to use when HRTF is enabled. Blank means use the default. This setting has no effect if HRTF is not enabled based on the hrtf enable setting. -Allowed values for this field are enumerated in openmw.log file is an HRTF enabled ausio system is installed. +Allowed values for this field are enumerated in openmw.log file is an HRTF enabled audio system is installed. The default value is empty, which uses the default profile. This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index 7f641888ac..687f55e5e3 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -12,7 +12,8 @@ Controls whether the engine will use paging and LOD algorithms to load the terra Otherwise, only the terrain of the surrounding cells is loaded. .. note:: - When enabling distant terrain, make sure the 'viewing distance' in the camera section is set to a larger value so that you can actually see the additional terrain. + When enabling distant terrain, make sure the 'viewing distance' in the camera section is set to a larger value so + that you can actually see the additional terrain. To avoid frame drops as the player moves around, nearby terrain pages are always preloaded in the background, regardless of the preloading settings in the 'Cells' section, diff --git a/docs/source/reference/modding/settings/video.rst b/docs/source/reference/modding/settings/video.rst index 3eb7c51289..a8a95739ea 100644 --- a/docs/source/reference/modding/settings/video.rst +++ b/docs/source/reference/modding/settings/video.rst @@ -170,7 +170,8 @@ contrast This setting controls the contrast correction for all video in the game. -This setting can only be configured by editing the settings configuration file. It has been reported to not work on some Linux systems. +This setting can only be configured by editing the settings configuration file. +It has been reported to not work on some Linux systems. gamma ----- @@ -183,4 +184,5 @@ This setting controls the gamma correction for all video in the game. Gamma is an exponent that makes colors brighter if greater than 1.0 and darker if less than 1.0. This setting can be changed in the Detail tab of the Video panel of the Options menu. -It has been reported to not work on some Linux systems, and therefore the in-game setting in the Options menu has been disabled on Linux systems. +It has been reported to not work on some Linux systems, +and therefore the in-game setting in the Options menu has been disabled on Linux systems. diff --git a/docs/source/reference/modding/settings/windows.rst b/docs/source/reference/modding/settings/windows.rst index 2fae9c3e31..2a60745c2e 100644 --- a/docs/source/reference/modding/settings/windows.rst +++ b/docs/source/reference/modding/settings/windows.rst @@ -25,7 +25,8 @@ Hand editing the configuration file might result in some fine tuning for alignme but the settings will be overwritten if a window is moved. .. note:: - To scale the windows, making the widgets proportionally larger, see the scaling factor setting in the GUI section instead. + To scale the windows, making the widgets proportionally larger, + see the scaling factor setting in the GUI section instead. :Type: boolean :Range: True/False @@ -210,7 +211,7 @@ dialogue w = 0.45 -The dialog window, for talking with NPCs. +The dialogue window, for talking with NPCs. Activated by clicking on a NPC. alchemy diff --git a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst index 421fa67a70..5fbff38d4e 100644 --- a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst +++ b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst @@ -20,29 +20,43 @@ General introduction to normal map conversion :Authors: Joakim (Lysol) Berg :Updated: 2016-11-11 -This page has general information and tutorials on how normal mapping works in OpenMW and how you can make mods using the old fake normal mapping technique (such as `Netch Bump mapped`_ and `Hlaalu Bump mapped`_, and maybe the most (in)famous one to give shiny rocks in OpenMW, the mod `On the Rocks`_!, featured in MGSO and Morrowind Rebirth) work in OpenMW. +This page has general information and tutorials on how normal mapping works in OpenMW and how you can make mods using +the old fake normal mapping technique (such as `Netch Bump mapped`_ and `Hlaalu Bump mapped`_, and maybe the most +(in)famous one to give shiny rocks in OpenMW, the mod `On the Rocks`_!, featured in MGSO and Morrowind Rebirth) work in OpenMW. *Note:* The conversion made in the `Converting Apel's Various Things - Sacks`_-part of this tutorial require the use of the application NifSkope_. -*Another note:* I will use the terms bump mapping and normal mapping simultaneously. Normal mapping is one form of bump mapping. In other words, normal mapping is bump mapping, but bump mapping isn't necessarily normal mapping. There are several techniques for bump mapping, and normal mapping is the most common one today. +*Another note:* I will use the terms bump mapping and normal mapping simultaneously. +Normal mapping is one form of bump mapping. In other words, normal mapping is bump mapping, +but bump mapping isn't necessarily normal mapping. +There are several techniques for bump mapping, and normal mapping is the most common one today. So let's get on with it. Normal Mapping in OpenMW ************************ -Normal mapping in OpenMW works in a very simple way: The engine just looks for a texture with a *_n.dds* suffix, and you're done. +Normal mapping in OpenMW works in a very simple way: The engine just looks for a texture with a *_n.dds* suffix, +and you're done. So to expand on this a bit, let's take a look at how a model seeks for textures. -Let us assume we have the model *example.nif*. In this model file, there should be a tag (NiSourceTexture) that states what texture it should use and where to find it. Typically, it will point to something like *exampletexture_01.dds*. This texture is supposed to be located directly in the Textures folder since it does not state anything else. If the model is a custom made one, modders tend to group their textures in separate folders, just to easily keep track of them. It might be something like *./Textures/moddername/exampletexture_02.dds*. +Let us assume we have the model *example.nif*. In this model file, +there should be a tag (NiSourceTexture) that states what texture it should use and where to find it. Typically, +it will point to something like *exampletexture_01.dds*. This texture is supposed to be located directly in the +Textures folder since it does not state anything else. If the model is a custom made one, modders tend to group +their textures in separate folders, just to easily keep track of them. +It might be something like *./Textures/moddername/exampletexture_02.dds*. -When OpenMW finally adds normal mapping, it simply takes the NiSourceTexture file path, e.g., *exampletexture_01.dds*, and looks for a *exampletexture_01_n.dds*. If it can't find this file, no normal mapping is added. If it *does* find this file, the model will use this texture as a normal map. Simple. +When OpenMW finally adds normal mapping, it simply takes the NiSourceTexture file path, e.g., +*exampletexture_01.dds*, and looks for a *exampletexture_01_n.dds*. If it can't find this file, no normal mapping is added. +If it *does* find this file, the model will use this texture as a normal map. Simple. Activating normal mapping shaders in OpenMW ******************************************* -Before normal (and specular and parallax) maps will show up in OpenMW, you'll need to activate them in the settings.cfg_-file. Add these rows where it would make sense: +Before normal (and specular and parallax) maps will show up in OpenMW, you'll need to activate them in the +settings.cfg_-file. Add these rows where it would make sense: :: @@ -72,13 +86,26 @@ Normal mapping in Morrowind with Morrowind Code Patch **Conversion difficulty:** *Varies. Sometimes quick and easy, sometimes time-consuming and hard.* -You might have bumped (pun intended) on a few bump-mapped texture packs for Morrowind that require the Morrowind Code Patch (MCP). You might even be thinking: Why doesn't OpenMW just support these instead of reinventing the wheel? I know it sounds strange, but it will make sense. Here's how MCP handles normal maps: +You might have bumped (pun intended) on a few bump-mapped texture packs for Morrowind that require the +Morrowind Code Patch (MCP). You might even be thinking: Why doesn't OpenMW just support these instead of reinventing +the wheel? I know it sounds strange, but it will make sense. Here's how MCP handles normal maps: -Morrowind does not recognize normal maps (they weren't really a "thing" yet in 2002), so even if you have a normal map, Morrowind will not load and display it. MCP has a clever way to solve this issue, by using something Morrowind *does* support, namely environment maps. You could add a tag for an environment map and then add a normal map as the environment map, but you'd end up with a shiny ugly model in the game. MCP solves this by turning down the brightness of the environment maps, making the model look *kind of* as if it had a normal map applied to it. I say kind of because it does not really look as good as normal mapping usually does. It was a hacky way to do it, but it was the only way at the time, and therefore the best way. +Morrowind does not recognize normal maps (they weren't really a "thing" yet in 2002), so even if you have a normal map, +Morrowind will not load and display it. MCP has a clever way to solve this issue, by using something Morrowind *does* support, +namely environment maps. You could add a tag for an environment map and then add a normal map as the environment map, +but you'd end up with a shiny ugly model in the game. MCP solves this by turning down the brightness of the environment maps, +making the model look *kind of* as if it had a normal map applied to it. +I say kind of because it does not really look as good as normal mapping usually does. It was a hacky way to do it, +but it was the only way at the time, and therefore the best way. -The biggest problem with this is not that it doesn't look as good as it could – no, the biggest problem in my opinion is that it requires you to state the file paths for your normal map textures *in the models*! For buildings, which often use several textures for one single model file, it could take *ages* to do this, and you had to do it for dozens of model files too. You also had to ship your texture pack with model files, making your mod bigger in file size. +The biggest problem with this is not that it doesn't look as good as it could – no, +the biggest problem in my opinion is that it requires you to state the file paths for your normal map textures *in the models*! +For buildings, which often use several textures for one single model file, it could take *ages* to do this, +and you had to do it for dozens of model files too. You also had to ship your texture pack with model files, +making your mod bigger in file size. -These are basically the reasons why OpenMW does not support fake bump maps like MCP does. It is just a really bad way to enhance your models, all the more when you have the possibility to do it in a better way. +These are basically the reasons why OpenMW does not support fake bump maps like MCP does. +It is just a really bad way to enhance your models, all the more when you have the possibility to do it in a better way. Normal mapping in Morrowind with MGE XE *************************************** @@ -86,9 +113,18 @@ Normal mapping in Morrowind with MGE XE **Conversion difficulty:** *Easy* -The most recent feature on this topic is that the Morrowind Graphics Extender (MGE) finally started to support real normal mapping in an experimental version available here: `MGE XE`_ (you can't use MGE with OpenMW!). Not only this but it also adds full support for physically based rendering (PBR), making it one step ahead of OpenMW in terms of texturing techniques. However, OpenMW will probably have this feature in the future too – and let's hope that OpenMW and MGE will handle PBR in a similar fashion in the future so that mods can be used for both MGE and OpenMW without any hassle. +The most recent feature on this topic is that the Morrowind Graphics Extender (MGE) finally started to support real +normal mapping in an experimental version available here: `MGE XE`_ (you can't use MGE with OpenMW!). +Not only this but it also adds full support for physically based rendering (PBR), +making it one step ahead of OpenMW in terms of texturing techniques. However, +OpenMW will probably have this feature in the future too – and let's hope that OpenMW and MGE will handle PBR in a +similar fashion in the future so that mods can be used for both MGE and OpenMW without any hassle. -I haven't researched that much on the MGE variant yet but it does support real implementation of normal mapping, making it really easy to convert mods made for MGE into OpenMW (I'm only talking about the normal map textures though). There's some kind of text file if I understood it correctly that MGE uses to find the normal map. OpenMW does not need this, you just have to make sure the normal map has the same name as the diffuse texture but with the correct suffix after. +I haven't researched that much on the MGE variant yet but it does support real implementation of normal mapping, +making it really easy to convert mods made for MGE into OpenMW (I'm only talking about the normal map textures though). +There's some kind of text file if I understood it correctly that MGE uses to find the normal map. +OpenMW does not need this, you just have to make sure the normal map has the same name as the diffuse texture but with +the correct suffix after. Now, on to the tutorials. @@ -99,14 +135,21 @@ Converting PeterBitt's Scamp Replacer :Authors: Joakim (Lysol) Berg :Updated: 2016-11-11 -So, let's say you've found out that PeterBitt_ makes awesome models and textures featuring physically based rendering (PBR) and normal maps. Let's say that you tried to run his `PBR Scamp Replacer`_ in OpenMW and that you were greatly disappointed when the normal map didn't seem to work. Lastly, let's say you came here, looking for some answers. Am I right? Great. Because you've come to the right place! +So, let's say you've found out that PeterBitt_ makes awesome models and textures featuring physically based rendering +(PBR) and normal maps. Let's say that you tried to run his `PBR Scamp Replacer`_ in OpenMW and that you were greatly +disappointed when the normal map didn't seem to work. Lastly, let's say you came here, looking for some answers. +Am I right? Great. Because you've come to the right place! -*A quick note before we begin*: Please note that you can only use the normal map texture and not the rest of the materials, since PBR isn't implemented in OpenMW yet. Sometimes PBR textures can look dull without all of the texture files, so have that in mind. +*A quick note before we begin*: Please note that you can only use the normal map texture and not the rest of the materials, +since PBR isn't implemented in OpenMW yet. Sometimes PBR textures can look dull without all of the texture files, +so have that in mind. Tutorial - MGE ************** -In this tutorial, I will use PeterBitt's `PBR Scamp Replacer`_ as an example, but any mod featuring PBR that requires the PBR version of MGE will do, provided it also includes a normal map (which it probably does). +In this tutorial, I will use PeterBitt's `PBR Scamp Replacer`_ as an example, +but any mod featuring PBR that requires the PBR version of MGE will do, +provided it also includes a normal map (which it probably does). So, follow these steps: @@ -120,11 +163,14 @@ So, follow these steps: #. Rename your newly extracted file (``tx_Scamp_normals.dds``) to ``tx_Scamp_n.dds`` (which is exactly the same name as the diffuse texture file, except for the added *_n* suffix before the filename extention). #. You're actually done! -So as you might notice, converting these mods is very simple and takes just a couple of minutes. It's more or less just a matter of renaming and moving a few files. +So as you might notice, converting these mods is very simple and takes just a couple of minutes. +It's more or less just a matter of renaming and moving a few files. -I totally recommend you to also try this on PeterBitt's Nix Hound replacer and Flash3113's various replacers. It should be the same principle to get those to work. +I totally recommend you to also try this on PeterBitt's Nix Hound replacer and Flash3113's various replacers. +It should be the same principle to get those to work. -And let's hope that some one implements PBR shaders to OpenMW too, so that we can use all the material files of these mods in the future. +And let's hope that some one implements PBR shaders to OpenMW too, +so that we can use all the material files of these mods in the future. Converting Lougian's Hlaalu Bump mapped --------------------------------------- @@ -133,12 +179,17 @@ Converting Lougian's Hlaalu Bump mapped :Authors: Joakim (Lysol) Berg :Updated: 2016-11-11 -Converting textures made for the Morrowind Code Patch (MCP) fake bump mapping can be really easy or a real pain, depending on a few circumstances. In this tutorial, we will look at a very easy, although in some cases a bit time-consuming, example. +Converting textures made for the Morrowind Code Patch (MCP) fake bump mapping can be really easy or a real pain, +depending on a few circumstances. In this tutorial, we will look at a very easy, +although in some cases a bit time-consuming, example. Tutorial - MCP, Part 1 ********************** -We will be converting a quite popular texture replacer of the Hlaalu architecture, namely Lougian's `Hlaalu Bump mapped`_. Since this is just a texture pack and not a model replacer, we can convert the mod in a few minutes by just renaming a few dozen files and by *not* extracting the included model (``.nif``) files when installing the mod. +We will be converting a quite popular texture replacer of the Hlaalu architecture, namely Lougian's `Hlaalu Bump mapped`_. +Since this is just a texture pack and not a model replacer, +we can convert the mod in a few minutes by just renaming a few dozen files and by *not* extracting the included model +(``.nif``) files when installing the mod. #. Download Lougian's `Hlaalu Bump mapped`_. #. Install the mod by extracting the ``./Textures`` folder to a data folder the way you usually install mods (**Pro tip**: Install using OpenMW's `Multiple data folders`_ function!). @@ -149,7 +200,8 @@ We will be converting a quite popular texture replacer of the Hlaalu architectur - As a nice bonus to this tutorial, this pack actually included one specularity texture too. We should use it of course. It's the one called "``tx_glass_amber_02_reflection.dds``". For OpenMW to recognize this file and use it as a specular map, you need to change the *_reflection.dds* part to *_spec.dds*, resulting in the name ``tx_glass_amber_01_spec.dds``. #. That should be it. Really simple, but I do know that it takes a few minutes to rename all those files. -Now – if the mod you want to change includes custom made models it gets a bit more complicated I'm afraid. But that is for the next tutorial. +Now – if the mod you want to change includes custom made models it gets a bit more complicated I'm afraid. +But that is for the next tutorial. Converting Apel's Various Things - Sacks ---------------------------------------- @@ -158,12 +210,16 @@ Converting Apel's Various Things - Sacks :Authors: Joakim (Lysol) Berg :Updated: 2016-11-09 -In part one of this tutorial, we converted a mod that only included modified Morrowind model (``.nif``) files so that the normal maps could be loaded in Morrowind with MCP. We ignored those model files since they are not needed with OpenMW. In this tutorial however, we will convert a mod that includes new, custom made models. In other words, we cannot just ignore those files this time. +In part one of this tutorial, we converted a mod that only included modified Morrowind model (``.nif``) +files so that the normal maps could be loaded in Morrowind with MCP. +We ignored those model files since they are not needed with OpenMW. In this tutorial however, +we will convert a mod that includes new, custom made models. In other words, we cannot just ignore those files this time. Tutorial - MCP, Part 2 ********************** -The sacks included in Apel's `Various Things - Sacks`_ come in two versions – Without bump mapping, and with bump mapping. Since we want the glory of normal mapping in our OpenMW setup, we will go with the bump-mapped version. +The sacks included in Apel's `Various Things - Sacks`_ come in two versions – Without bump mapping, and with bump mapping. +Since we want the glory of normal mapping in our OpenMW setup, we will go with the bump-mapped version. #. Start by downloading Apel's `Various Things - Sacks`_ from Nexus. #. Once downloaded, install it the way you'd normally install your mods (**Pro tip**: Install using OpenMW's `Multiple data folders`_ function!). @@ -181,7 +237,9 @@ The sacks included in Apel's `Various Things - Sacks`_ come in two versions – #. OpenMW detects normal maps if they have the same name as the base diffuse texture, but with a *_n.dds* suffix. In this mod, the normal maps has a suffix of *_nm.dds*. Change all the files that ends with *_nm.dds* to instead end with *_n.dds*. #. Finally, `we are done`_! -Since these models have one or two textures applied to them, the fix was not that time-consuming. It gets worse when you have to fix a model that uses loads of textures. The principle is the same, it just requires more manual work which is annoying and takes time. +Since these models have one or two textures applied to them, the fix was not that time-consuming. +It gets worse when you have to fix a model that uses loads of textures. The principle is the same, +it just requires more manual work which is annoying and takes time. .. _`Netch Bump mapped`: https://www.nexusmods.com/morrowind/mods/42851/? .. _`Hlaalu Bump mapped`: https://www.nexusmods.com/morrowind/mods/42396/? diff --git a/docs/source/reference/modding/texture-modding/texture-basics.rst b/docs/source/reference/modding/texture-modding/texture-basics.rst index d9137c89d8..51523af3f3 100644 --- a/docs/source/reference/modding/texture-modding/texture-basics.rst +++ b/docs/source/reference/modding/texture-modding/texture-basics.rst @@ -19,10 +19,16 @@ OpenMW automatically uses shaders for objects with these mapping techniques. Normal Mapping ############## -To plug in a normal map, you name the normal map as the diffuse texture but with a specified suffix after. OpenMW will then recognise the file and load it as a normal map, provided you have set up your settings file correctly. See the section `Automatic use`_ further down below for detailed information. +To plug in a normal map, you name the normal map as the diffuse texture but with a specified suffix after. +OpenMW will then recognise the file and load it as a normal map, provided you have set up your settings file correctly. +See the section `Automatic use`_ further down below for detailed information. .. note:: - While the original Morrowind engine does support the loading of a BumpTexture slot in the NIF, it will not display it as a normal map. Morrowind Code Patch (MCP) added a way to hack normal maps into the engine by first enabling the engine to load the BumpTexture slot as an environment map and then turn down the brightness of the environment map. This will imitate how a real normal map shader would display a normal map, but it will not look exactly the same. + While the original Morrowind engine does support the loading of a BumpTexture slot in the NIF, + it will not display it as a normal map. Morrowind Code Patch (MCP) + added a way to hack normal maps into the engine by first enabling the engine to load the BumpTexture slot as an + environment map and then turn down the brightness of the environment map. + This will imitate how a real normal map shader would display a normal map, but it will not look exactly the same. OpenMW uses standard normal mapping, which achieves much better results. Unfortunately, this difference can result in incompatibilities. Some mods From b6f23cd3661b7195f1068a4aff162e7518130beb Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 27 Oct 2018 13:27:25 +0300 Subject: [PATCH 25/31] Make constants usage more obvious --- apps/openmw/mwrender/animation.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 8ae8800a6c..8463b7bfe5 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -15,13 +15,13 @@ #include -#include - #include #include #include #include +#include + #include // KeyframeHolder #include @@ -1791,10 +1791,10 @@ namespace MWRender { // TODO: use global attenuation settings - // 1 pt of Light effect magnitude corresponds to 1 foot of light source radius, which is about 21.33 game units, - // but Morrowind uses imprecise value of foot for magic effects. - float radius = effect * 22.f; - float linearAttenuation = 3.f / radius; + // 1 pt of Light magnitude corresponds to 1 foot of radius + float radius = effect * std::ceil(Constants::UnitsPerFoot); + const float linearValue = 3.f; // Currently hardcoded: unmodified Morrowind attenuation settings + float linearAttenuation = linearValue / radius; if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation()) { From abdf40e0d54bfc4d8970a5ca742ad998586ce355 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 27 Oct 2018 16:33:26 +0300 Subject: [PATCH 26/31] Avoid making expensive visitEffectSources calls if no spell absorption effect is active --- apps/openmw/mwmechanics/spellcasting.cpp | 39 +++++++++++++----------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 4a74e1647f..7c9e207e96 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -472,28 +472,31 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->setEnemy(target); // Try absorbing if it's a spell - // NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, so use the same approach here + // Unlike Reflect, this is done once per spell absorption effect source bool absorbed = false; if (spell && caster != target && target.getClass().isActor()) { - GetAbsorptionProbability check(target); - MWMechanics::CreatureStats& stats = target.getClass().getCreatureStats(target); - stats.getActiveSpells().visitEffectSources(check); - stats.getSpells().visitEffectSources(check); - if (target.getClass().hasInventoryStore(target)) - target.getClass().getInventoryStore(target).visitEffectSources(check); - - int absorb = check.mProbability * 100; - absorbed = (Misc::Rng::roll0to99() < absorb); - if (absorbed) + CreatureStats& stats = target.getClass().getCreatureStats(target); + if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f) { - const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); - MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( - "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); - // Magicka is increased by cost of spell - DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); - magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); - target.getClass().getCreatureStats(target).setMagicka(magicka); + GetAbsorptionProbability check(target); + stats.getActiveSpells().visitEffectSources(check); + stats.getSpells().visitEffectSources(check); + if (target.getClass().hasInventoryStore(target)) + target.getClass().getInventoryStore(target).visitEffectSources(check); + + int absorb = check.mProbability * 100; + absorbed = (Misc::Rng::roll0to99() < absorb); + if (absorbed) + { + const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); + // Magicka is increased by cost of spell + DynamicStat magicka = stats.getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); + stats.setMagicka(magicka); + } } } From 7a7b47b0ba6a6582ac9ac501f31b2a67bc83c7ea Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 27 Oct 2018 20:31:03 +0400 Subject: [PATCH 27/31] Add missing changelog entries --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed387eaf63..4d49672e00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug #2256: Landing sound not playing when jumping immediately after landing Bug #2274: Thin platform clips through player character instead of lifting Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped + Bug #2446: Restore Attribute/Skill should allow restoring drained attributes Bug #2455: Creatures attacks degrade armor Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash Bug #2626: Resurrecting the player does not resume the game @@ -46,6 +47,7 @@ Bug #4110: Fixed undo / redo menu text losing the assigned shortcuts Bug #4125: OpenMW logo cropped on bugtracker Bug #4215: OpenMW shows book text after last EOL tag + Bug #4217: Fixme implementation differs from Morrowind's Bug #4221: Characters get stuck in V-shaped terrain Bug #4230: AiTravel package issues break some Tribunal quests Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality @@ -55,6 +57,7 @@ Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+ Bug #4286: Scripted animations can be interrupted Bug #4291: Non-persistent actors that started the game as dead do not play death animations + Bug #4292: CenterOnCell implementation differs from vanilla Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4304: "Follow" not working as a second AI package Bug #4307: World cleanup should remove dead bodies only if death animation is finished @@ -65,7 +68,7 @@ Bug #4368: Settings window ok button doesn't have key focus by default Bug #4378: On-self absorb spells restore stats Bug #4393: NPCs walk back to where they were after using ResetActors - Bug #4416: Handle exception if we try to play non-music file + Bug #4416: Non-music files crash the game when they are tried to be played Bug #4419: MRK NiStringExtraData is handled incorrectly Bug #4426: RotateWorld behavior is incorrect Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 @@ -81,6 +84,7 @@ Bug #4459: NotCell dialogue condition doesn't support partial matches Bug #4460: Script function "Equip" doesn't bypass beast restrictions Bug #4461: "Open" spell from non-player caster isn't a crime + Bug #4463: %g format doesn't return more digits Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages Bug #4467: Content selector: cyrillic characters are decoded incorrectly in plugin descriptions Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal From d27d48eb2259338bb1296bb67cacd0a53d839aa5 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 27 Oct 2018 19:45:40 +0300 Subject: [PATCH 28/31] Fix revert record icon --- files/opencs/resources.qrc | 1 + 1 file changed, 1 insertion(+) diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 93b47dbdda..4fc4bdf285 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -94,6 +94,7 @@ record-preview.png record-clone.png record-add.png + record-revert.png resources-icon.png resources-mesh.png resources-music.png From 8337423e6c273ed507c5178cc35c1065766bdc26 Mon Sep 17 00:00:00 2001 From: Alec Nunn Date: Sat, 27 Oct 2018 20:16:51 -0700 Subject: [PATCH 29/31] Fixed up grammar in the game settings documentation. --- docs/source/reference/modding/settings/game.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 475a2f175c..124b54f388 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -168,11 +168,11 @@ use additional anim sources :Range: True/False :Default: False -Allow to load additional animation sources when enabled. +Allow the engine to load additional animation sources when enabled. For example, if the main animation mesh has name Meshes/x.nif, -an engine will load all KF-files from Animations/x folder and its child folders. -Can be useful if you want to use several animation replacers without merging them. -Attention: animations from AnimKit have own format and are not supposed to be directly loaded in-game! +the engine will load all KF-files from Animations/x folder and its child folders. +This can be useful if you want to use several animation replacers without merging them. +Attention: animations from AnimKit have their own format and are not supposed to be directly loaded in-game! This setting can only be configured by editing the settings configuration file. barter disposition change is permanent @@ -184,6 +184,6 @@ barter disposition change is permanent If this setting is true, disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. -This imitates the option Morrowind Code Patch offers. +This imitates the option that Morrowind Code Patch offers. This setting can be toggled in Advanced tab of the launcher. From 6e05853478585f5eb2034771f1246dbc310c9de5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 28 Oct 2018 11:44:14 +0400 Subject: [PATCH 30/31] Center progress bar when there are active messageboxes (bug #4691) --- CHANGELOG.md | 1 + apps/openmw/mwbase/windowmanager.hpp | 2 ++ apps/openmw/mwgui/loadingscreen.cpp | 8 ++++++-- apps/openmw/mwgui/loadingscreen.hpp | 2 +- apps/openmw/mwgui/messagebox.cpp | 5 +++++ apps/openmw/mwgui/messagebox.hpp | 2 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 9 +++++++++ apps/openmw/mwgui/windowmanagerimp.hpp | 2 ++ apps/openmw/mwstate/statemanagerimp.cpp | 6 ++++-- apps/openmw/mwworld/scene.cpp | 6 ++++-- components/loadinglistener/loadinglistener.hpp | 2 +- 11 files changed, 37 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed387eaf63..2200bccb17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -147,6 +147,7 @@ Bug #4684: Spell Absorption is additive Bug #4685: Missing sound causes an exception inside Say command Bug #4689: Default creature soundgen entries are not used + Bug #4691: Loading bar for cell should be moved up when text is still active at bottom of screen Feature #912: Editor: Add missing icons to UniversalId tables Feature #1221: Editor: Creature/NPC rendering Feature #1617: Editor: Enchantment effect record verifier diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 36beb25fc8..2eec78526f 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -285,6 +285,8 @@ namespace MWBase virtual void setEnemy (const MWWorld::Ptr& enemy) = 0; + virtual int getMessagesCount() const = 0; + virtual const Translation::Storage& getTranslationDataStorage() const = 0; /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 3bb3ee260d..7f641d4088 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -96,7 +96,7 @@ namespace MWGui Log(Debug::Warning) << "Warning: no splash screens found!"; } - void LoadingScreen::setLabel(const std::string &label, bool important) + void LoadingScreen::setLabel(const std::string &label, bool important, bool center) { mImportantLabel = important; @@ -105,7 +105,11 @@ namespace MWGui MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight()); size.width = std::max(300, size.width); mLoadingBox->setSize(size); - mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mLoadingBox->getTop()); + + if (center) + mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight()/2 - mLoadingBox->getHeight()/2); + else + mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight() - mLoadingBox->getHeight() - 8); } void LoadingScreen::setVisible(bool visible) diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index bdd210d00d..e74a602067 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -34,7 +34,7 @@ namespace MWGui virtual ~LoadingScreen(); /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details - virtual void setLabel (const std::string& label, bool important); + virtual void setLabel (const std::string& label, bool important, bool center); virtual void loadingOn(bool visible=true); virtual void loadingOff(); virtual void setProgressRange (size_t range); diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index e83c4b238a..6c2d85dd9e 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -35,6 +35,11 @@ namespace MWGui } } + int MessageBoxManager::getMessagesCount() + { + return mMessageBoxes.size(); + } + void MessageBoxManager::clear() { if (mInterMessageBoxe) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 156a17e678..e4e4b743c2 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -28,6 +28,8 @@ namespace MWGui bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); + int getMessagesCount(); + const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; } /// Remove all message boxes diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 3937518552..f02314de63 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1670,6 +1670,15 @@ namespace MWGui mHud->setEnemy(enemy); } + int WindowManager::getMessagesCount() const + { + int count = 0; + if (mMessageBoxManager) + count = mMessageBoxManager->getMessagesCount(); + + return count; + } + Loading::Listener* WindowManager::getLoadingScreen() { return mLoadingScreen; diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index dc3cc4b09b..050f1667d7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -316,6 +316,8 @@ namespace MWGui virtual void setEnemy (const MWWorld::Ptr& enemy); + virtual int getMessagesCount() const; + virtual const Translation::Storage& getTranslationDataStorage() const; void onSoulgemDialogButtonPressed (int button); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 9804531d70..946c5eef7f 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -265,9 +265,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.save (stream); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); // Using only Cells for progress information, since they typically have the largest records by far listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells()); - listener.setLabel("#{sNotifyMessage4}", true); + listener.setLabel("#{sNotifyMessage4}", true, messagesCount > 0); Loading::ScopedLoad load(&listener); @@ -389,9 +390,10 @@ void MWState::StateManager::loadGame (const Character *character, const std::str std::map contentFileMap = buildContentFileIndexMap (reader); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); listener.setProgressRange(100); - listener.setLabel("#{sLoadingMessage14}"); + listener.setLabel("#{sLoadingMessage14}", false, messagesCount > 0); Loading::ScopedLoad load(&listener); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 30e73df588..4c6cf1962b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -360,8 +360,9 @@ namespace MWWorld Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); + int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); std::string loadingExteriorText = "#{sLoadingMessage3}"; - loadingListener->setLabel(loadingExteriorText); + loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0); CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) @@ -526,8 +527,9 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); std::string loadingInteriorText = "#{sLoadingMessage2}"; - loadingListener->setLabel(loadingInteriorText); + loadingListener->setLabel(loadingInteriorText, false, messagesCount > 0); Loading::ScopedLoad load(loadingListener); if(!loadcell) diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 6c7a3b090b..f5cfa5cf37 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -14,7 +14,7 @@ namespace Loading /// @note "non-important" labels may not show on screen if the loading process went so fast /// that the implementation decided not to show a loading screen at all. "important" labels /// will show in a separate message-box if the loading screen was not shown. - virtual void setLabel (const std::string& label, bool important=false) {} + virtual void setLabel (const std::string& label, bool important=false, bool center=false) {} /// Start a loading sequence. Must call loadingOff() when done. /// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically. From 06d226a1b72841b97f73ad35c777ee856957aa98 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 28 Oct 2018 15:08:24 +0400 Subject: [PATCH 31/31] Minor tweaks for actors processing range setting --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 5 +++++ files/mygui/openmw_settings_window.layout | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b707851deb..3ee1823d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -178,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 #4647: Cull actors outside of AI processing range 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 diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 1c75763167..3c540a9d4e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -17,6 +17,7 @@ #include "../mwworld/ptr.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" @@ -437,6 +438,10 @@ namespace MWMechanics { if (it->first == "Game" && it->second == "actors processing range") { + int state = MWBase::Environment::get().getStateManager()->getState(); + if (state != MWBase::StateManager::State_Running) + continue; + mActors.updateProcessingRange(); // Update mechanics for new processing range immediately diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 8e6e98612b..3c26648572 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -75,7 +75,7 @@ - + @@ -87,7 +87,7 @@ - + @@ -224,7 +224,7 @@ - +