From 36cea2073f598cc9768f88419a9e4d14b68c0682 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 13 Mar 2021 12:10:14 +0000 Subject: [PATCH 01/96] Update MyGUI Includes https://github.com/MyGUI/mygui/commit/f01cba4bb392ae990893442f5bcdf05bfc7a1656 Fixes https://gitlab.com/OpenMW/openmw/-/issues/5897 --- extern/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index bf18e4136..7e9f189fe 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -63,10 +63,11 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) set(BUILD_SHARED_LIBS ON) endif() + # master on 13 Mar 2021 include(FetchContent) FetchContent_Declare(mygui - URL https://github.com/MyGUI/mygui/archive/MyGUI3.4.1.zip - URL_HASH MD5=952d4033854612c99a5d9bf4b8550c26 + URL https://github.com/MyGUI/mygui/archive/59c1388b942721887d18743ada15f1906ff11a1f.zip + URL_HASH MD5=0a64c9cccc8f96dc8c08172175e68e1c SOURCE_DIR fetched/mygui ) FetchContent_MakeAvailableExcludeFromAll(mygui) From e30a59772c6d18f1953b0e4b6afc55be439e7c04 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 8 Jan 2021 22:18:37 +0100 Subject: [PATCH 02/96] Remove DEFAULT_DECODER macros --- apps/openmw/mwsound/ffmpeg_decoder.hpp | 6 ++---- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 92d046d85..f099c831c 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -69,15 +69,13 @@ namespace MWSound FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs); - FFmpeg_Decoder(const VFS::Manager* vfs); public: + explicit FFmpeg_Decoder(const VFS::Manager* vfs); + virtual ~FFmpeg_Decoder(); friend class SoundManager; }; -#ifndef DEFAULT_DECODER -#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder) -#endif } #endif diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 44465e5a7..740167b0a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -117,7 +117,7 @@ namespace MWSound // Return a new decoder instance, used as needed by the output implementations DecoderPtr SoundManager::getDecoder() { - return DecoderPtr(new DEFAULT_DECODER (mVFS)); + return std::make_shared(mVFS); } DecoderPtr SoundManager::loadVoice(const std::string &voicefile) From b0311ce9f1ed2c562fac6f8b99127dfbf8a79cf8 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Jan 2021 00:48:45 +0100 Subject: [PATCH 03/96] Remove DEFAULT_OUTPUT macros --- apps/openmw/mwsound/openal_output.hpp | 3 --- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index d9ca924a7..2a19e6768 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -98,9 +98,6 @@ namespace MWSound OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); }; -#ifndef DEFAULT_OUTPUT -#define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x)) -#endif } #endif diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 740167b0a..6f029d0f6 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -54,7 +54,7 @@ namespace MWSound SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound) : mVFS(vfs) - , mOutput(new DEFAULT_OUTPUT(*this)) + , mOutput(new OpenAL_Output(*this)) , mWaterSoundUpdater(makeWaterSoundUpdaterSettings()) , mSoundBuffers(*vfs, *mOutput) , mListenerUnderwater(false) From 9275dd2dcb6499ee23aba7f6ec056552352034aa Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Jan 2021 21:53:23 +0100 Subject: [PATCH 04/96] Avoid virtual dispatch in SoundManager dtor --- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 6f029d0f6..fc9735d57 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -109,7 +109,7 @@ namespace MWSound SoundManager::~SoundManager() { - clear(); + SoundManager::clear(); mSoundBuffers.clear(); mOutput.reset(); } From 845e3944d6d61488f024d73923dae975a3d12387 Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 19 Mar 2021 17:54:08 -0400 Subject: [PATCH 05/96] make refraction more visible even at a distance --- files/shaders/water_fragment.glsl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index d9b9463ad..5f6d8c6ac 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -236,7 +236,11 @@ void main(void) if (cameraPos.z < 0.0) refraction = clamp(refraction * 1.5, 0.0, 1.0); else - refraction = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0)); + { + vec3 refractionA = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0)); + vec3 refractionB = mix(refraction, waterColor, clamp(realWaterDepth/VISIBILITY, 0.0, 1.0)); + refraction = mix(refractionA, refractionB, 0.8); + } // sunlight scattering // normal for sunlight scattering From bf336e4cb45575b67903e611675d0b356d79d8cd Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 19 Mar 2021 18:51:52 -0400 Subject: [PATCH 06/96] make sun scattering color stop being an ugly radioactive green --- files/shaders/water_fragment.glsl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 5f6d8c6ac..b7c748751 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -29,9 +29,6 @@ const float BUMP_RAIN = 2.5; const float REFL_BUMP = 0.10; // reflection distortion amount const float REFR_BUMP = 0.07; // refraction distortion amount -const float SCATTER_AMOUNT = 0.3; // amount of sunlight scattering -const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering - const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SPEC_HARDNESS = 256.0; // specular highlights hardness @@ -43,6 +40,9 @@ const float WIND_SPEED = 0.2f; const vec3 WATER_COLOR = vec3(0.090195, 0.115685, 0.12745); +const float SCATTER_AMOUNT = 0.5; // amount of sunlight scattering +const vec3 SCATTER_COLOUR = WATER_COLOR * 8.0; // colour of sunlight scattering + // ---------------- rain ripples related stuff --------------------- const float RAIN_RIPPLE_GAPS = 5.0; From 1a4e9df707a4efe2cf092a252ccb66781906e466 Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 19 Mar 2021 20:12:40 -0400 Subject: [PATCH 07/96] add bit to suppress coastline artifacts at more of a distance --- files/shaders/water_fragment.glsl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index b7c748751..db208ea42 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -34,6 +34,7 @@ const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SPEC_HARDNESS = 256.0; // specular highlights hardness const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be suppressed for reflections and refractions (prevents artifacts at shores) +const float BUMP_SUPPRESS_DEPTH_SS = 1000.0; // modifier using screenspace depth (helps prevent same artifacts but at higher distances) const vec2 WIND_DIR = vec2(0.5f, -0.8f); const float WIND_SPEED = 0.2f; @@ -218,7 +219,10 @@ void main(void) float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-screenCoordsOffset).x) * radialise; float surfaceDepth = linearizeDepth(gl_FragCoord.z) * radialise; float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum - screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); + screenCoordsOffset *= clamp( + realWaterDepth / (BUMP_SUPPRESS_DEPTH + * max(1, depthSample / BUMP_SUPPRESS_DEPTH_SS)) // suppress more at distance + ,0 ,1); #endif // reflection vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; From 675c0ab72fd365c3699a68081636db7d64dd175e Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Mar 2021 23:23:26 +0100 Subject: [PATCH 08/96] Apply uniform random deviation to AI reaction timer This allows to distribute AI reaction calls over time. Before this change actors appearing at the same frame will react in the same frame over and over because AI reaction period is constant. It creates a non-uniform CPU usage over frames. If a single frame has too many AI reactions it may cause stuttering when there are too many actors on a scene for current system. Another concern is a synchronization of actions between creatures and NPC. They start to go or hit at the same frame that is unnatural. --- apps/openmw/mwmechanics/aicombat.cpp | 15 ++-------- apps/openmw/mwmechanics/aicombat.hpp | 4 +-- apps/openmw/mwmechanics/aipackage.cpp | 9 ++---- apps/openmw/mwmechanics/aipackage.hpp | 5 ++-- apps/openmw/mwmechanics/aitimer.hpp | 26 +++++++++++++++++ apps/openmw/mwmechanics/aiwander.cpp | 11 ++----- apps/openmw/mwmechanics/aiwander.hpp | 4 +-- components/misc/rng.cpp | 5 ++++ components/misc/rng.hpp | 2 ++ components/misc/timer.hpp | 42 +++++++++++++++++++++++++++ 10 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 apps/openmw/mwmechanics/aitimer.hpp create mode 100644 components/misc/timer.hpp diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 58a908672..51fcb92c1 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -146,19 +146,10 @@ namespace MWMechanics } storage.mActionCooldown -= duration; - float& timerReact = storage.mTimerReact; - if (timerReact < AI_REACTION_TIME) - { - timerReact += duration; - } - else - { - timerReact = 0; - if (attack(actor, target, storage, characterController)) - return true; - } + if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting) + return false; - return false; + return attack(actor, target, storage, characterController); } bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 3a77aa8e8..0f42c6e2d 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -10,6 +10,7 @@ #include "pathfinding.hpp" #include "movement.hpp" #include "obstacle.hpp" +#include "aitimer.hpp" namespace ESM { @@ -27,7 +28,7 @@ namespace MWMechanics struct AiCombatStorage : AiTemporaryBase { float mAttackCooldown; - float mTimerReact; + AiReactionTimer mReaction; float mTimerCombatMove; bool mReadyToAttack; bool mAttack; @@ -60,7 +61,6 @@ namespace MWMechanics AiCombatStorage(): mAttackCooldown(0.0f), - mTimerReact(AI_REACTION_TIME), mTimerCombatMove(0.0f), mReadyToAttack(false), mAttack(false), diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 99132b711..00e1c9c3a 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -28,7 +28,6 @@ MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : mTypeId(typeId), mOptions(options), - mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild mTargetActorRefId(""), mTargetActorId(-1), mRotateOnTheRunChecks(0), @@ -64,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const void MWMechanics::AiPackage::reset() { // reset all members - mTimer = AI_REACTION_TIME + 1.0f; + mReaction.reset(); mIsShortcutting = false; mShortcutProhibited = false; mShortcutFailPos = osg::Vec3f(); @@ -75,7 +74,7 @@ void MWMechanics::AiPackage::reset() bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance) { - mTimer += duration; //Update timer + const Misc::TimerStatus timerStatus = mReaction.update(duration); const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -98,7 +97,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const bool isDestReached = (distToTarget <= destTolerance); const bool actorCanMoveByZ = canActorMoveByZAxis(actor); - if (!isDestReached && mTimer > AI_REACTION_TIME) + if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed) { if (actor.getClass().isBipedal(actor)) openDoors(actor); @@ -142,8 +141,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go } } - - mTimer = 0; } const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 4201de5c8..81b09c8b9 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -10,6 +10,7 @@ #include "obstacle.hpp" #include "aistate.hpp" #include "aipackagetypeid.hpp" +#include "aitimer.hpp" namespace MWWorld { @@ -28,8 +29,6 @@ namespace ESM namespace MWMechanics { - const float AI_REACTION_TIME = 0.25f; - class CharacterController; class PathgridGraph; @@ -158,7 +157,7 @@ namespace MWMechanics PathFinder mPathFinder; ObstacleCheck mObstacleCheck; - float mTimer; + AiReactionTimer mReaction; std::string mTargetActorRefId; mutable int mTargetActorId; diff --git a/apps/openmw/mwmechanics/aitimer.hpp b/apps/openmw/mwmechanics/aitimer.hpp new file mode 100644 index 000000000..804cda1bd --- /dev/null +++ b/apps/openmw/mwmechanics/aitimer.hpp @@ -0,0 +1,26 @@ +#ifndef OPENMW_MECHANICS_AITIMER_H +#define OPENMW_MECHANICS_AITIMER_H + +#include +#include + +namespace MWMechanics +{ + constexpr float AI_REACTION_TIME = 0.25f; + + class AiReactionTimer + { + public: + static constexpr float sDeviation = 0.1f; + + Misc::TimerStatus update(float duration) { return mImpl.update(duration); } + + void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); } + + private: + Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation, + Misc::Rng::deviate(0, sDeviation)}; + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 375209a25..72b8757bf 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -223,15 +223,10 @@ namespace MWMechanics doPerFrameActionsForState(actor, duration, storage); - float& lastReaction = storage.mReaction; - lastReaction += duration; - if (AI_REACTION_TIME <= lastReaction) - { - lastReaction = 0; - return reactionTimeActions(actor, storage, pos); - } - else + if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting) return false; + + return reactionTimeActions(actor, storage, pos); } bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 8e718061e..52a926d14 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -10,6 +10,7 @@ #include "pathfinding.hpp" #include "obstacle.hpp" #include "aistate.hpp" +#include "aitimer.hpp" namespace ESM { @@ -25,7 +26,7 @@ namespace MWMechanics /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. struct AiWanderStorage : AiTemporaryBase { - float mReaction; // update some actions infrequently + AiReactionTimer mReaction; // AiWander states enum WanderState @@ -57,7 +58,6 @@ namespace MWMechanics int mStuckCount; AiWanderStorage(): - mReaction(0), mState(Wander_ChooseAction), mIsWanderingManually(false), mCanWanderAlongPathGrid(true), diff --git a/components/misc/rng.cpp b/components/misc/rng.cpp index 23d820448..4805f0d91 100644 --- a/components/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -47,4 +47,9 @@ namespace Misc { return static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); } + + float Rng::deviate(float mean, float deviation, Seed& seed) + { + return std::uniform_real_distribution(mean - deviation, mean + deviation)(seed.mGenerator); + } } diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index 8efca438d..998ac0d53 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -42,6 +42,8 @@ public: /// returns default seed for RNG static unsigned int generateDefaultSeed(); + + static float deviate(float mean, float deviation, Seed& seed = getSeed()); }; } diff --git a/components/misc/timer.hpp b/components/misc/timer.hpp new file mode 100644 index 000000000..81a2ca073 --- /dev/null +++ b/components/misc/timer.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_COMPONENTS_MISC_TIMER_H +#define OPENMW_COMPONENTS_MISC_TIMER_H + +#include "rng.hpp" + +namespace Misc +{ + enum class TimerStatus + { + Waiting, + Elapsed, + }; + + class DeviatingPeriodicTimer + { + public: + explicit DeviatingPeriodicTimer(float period, float deviation, float timeLeft) + : mPeriod(period), mDeviation(deviation), mTimeLeft(timeLeft) + {} + + TimerStatus update(float duration) + { + if (mTimeLeft > 0) + { + mTimeLeft -= duration; + return TimerStatus::Waiting; + } + + mTimeLeft = Rng::deviate(mPeriod, mDeviation); + return TimerStatus::Elapsed; + } + + void reset(float timeLeft) { mTimeLeft = timeLeft; } + + private: + const float mPeriod; + const float mDeviation; + float mTimeLeft; + }; +} + +#endif From 62c0ecbbd0556b16db5a01798f41b4e94921565e Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 20 Mar 2021 01:50:51 +0100 Subject: [PATCH 09/96] Separate engage combat timer for each actor Use DeviatingPeriodicTimer to distribute calls over time. This reduces stuttering and make AI more natural. --- apps/openmw/mwmechanics/actor.hpp | 8 ++++++++ apps/openmw/mwmechanics/actors.cpp | 8 +++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/actor.hpp b/apps/openmw/mwmechanics/actor.hpp index 287ca420f..be4f42537 100644 --- a/apps/openmw/mwmechanics/actor.hpp +++ b/apps/openmw/mwmechanics/actor.hpp @@ -5,6 +5,8 @@ #include "../mwmechanics/actorutil.hpp" +#include + namespace MWRender { class Animation; @@ -41,12 +43,18 @@ namespace MWMechanics bool isTurningToPlayer() const; void setTurningToPlayer(bool turning); + Misc::TimerStatus updateEngageCombatTimer(float duration) + { + return mEngageCombat.update(duration); + } + private: std::unique_ptr mCharacterController; int mGreetingTimer{0}; float mTargetAngleRadians{0.f}; GreetingState mGreetingState{Greet_None}; bool mIsTurningToPlayer{false}; + Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)}; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c0a137158..4379521a4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1901,14 +1901,11 @@ namespace MWMechanics { if(!paused) { - static float timerUpdateAITargets = 0; static float timerUpdateHeadTrack = 0; static float timerUpdateEquippedLight = 0; static float timerUpdateHello = 0; const float updateEquippedLightInterval = 1.0f; - // target lists get updated once every 1.0 sec - if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0; if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; if (timerUpdateHello >= 0.25f) timerUpdateHello = 0; if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0; @@ -1961,6 +1958,8 @@ namespace MWMechanics iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); + const Misc::TimerStatus engageCombatTimerStatus = iter->second->updateEngageCombatTimer(duration); + // For dead actors we need to update looping spell particles if (iter->first.getClass().getCreatureStats(iter->first).isDead()) { @@ -1989,7 +1988,7 @@ namespace MWMechanics } if (aiActive && inProcessingRange) { - if (timerUpdateAITargets == 0) + if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed) { if (!isPlayer) adjustCommandedActor(iter->first); @@ -2076,7 +2075,6 @@ namespace MWMechanics if (avoidCollisions) predictAndAvoidCollisions(); - timerUpdateAITargets += duration; timerUpdateHeadTrack += duration; timerUpdateEquippedLight += duration; timerUpdateHello += duration; From 40265bf1181167d927f3870b9d5127e310ce49f7 Mon Sep 17 00:00:00 2001 From: wareya Date: Sat, 20 Mar 2021 21:14:56 -0400 Subject: [PATCH 10/96] make unstucking slightly smarter (can turn itself off, also acts like flat ground) --- apps/openmw/mwphysics/actor.hpp | 21 +++++++++++++++++++ apps/openmw/mwphysics/movementsolver.cpp | 26 +++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 031125f40..472a79bff 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -159,6 +159,24 @@ namespace MWPhysics MWWorld::Ptr getStandingOnPtr() const; void setStandingOnPtr(const MWWorld::Ptr& ptr); + unsigned int getStuckFrames() const + { + return mStuckFrames; + } + void setStuckFrames(unsigned int frames) + { + mStuckFrames = frames; + } + + const osg::Vec3f &getLastStuckPosition() const + { + return mLastStuckPosition; + } + void setLastStuckPosition(osg::Vec3f position) + { + mLastStuckPosition = position; + } + private: MWWorld::Ptr mStandingOnPtr; /// Removes then re-adds the collision object to the dynamics world @@ -192,6 +210,9 @@ namespace MWPhysics btTransform mLocalTransform; mutable std::mutex mPositionMutex; + unsigned int mStuckFrames; + osg::Vec3f mLastStuckPosition; + osg::Vec3f mForce; std::atomic mOnGround; std::atomic mOnSlope; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 5f0322a1f..a393ec578 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -204,7 +204,7 @@ namespace MWPhysics osg::Vec3f lastSlideNormalFallback(0,0,1); bool forceGroundTest = false; - for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) + for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations) { osg::Vec3f nextpos = newPosition + velocity * remainingTime; @@ -394,6 +394,12 @@ namespace MWPhysics isOnGround = false; } } + // forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things can/will break ground detection + if(physicActor->getStuckFrames() > 0) + { + isOnGround = true; + isOnSlope = false; + } } if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying) @@ -437,6 +443,17 @@ namespace MWPhysics auto* collisionObject = physicActor->getCollisionObject(); auto tempPosition = actor.mPosition; + if(physicActor->getStuckFrames() >= 10) + { + if((physicActor->getLastStuckPosition() - actor.mPosition).length2() < 100) + return; + else + { + physicActor->setStuckFrames(0); + physicActor->setLastStuckPosition({0, 0, 0}); + } + } + // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver) // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); @@ -470,6 +487,8 @@ namespace MWPhysics auto contactCallback = gatherContacts({0.0, 0.0, 0.0}); if(contactCallback.mDistance < -sAllowedPenetration) { + physicActor->setStuckFrames(physicActor->getStuckFrames() + 1); + physicActor->setLastStuckPosition(actor.mPosition); // we are; try moving it out of the world auto positionDelta = contactCallback.mContactSum; // limit rejection delta to the largest known individual rejections @@ -502,6 +521,11 @@ namespace MWPhysics } } } + else + { + physicActor->setStuckFrames(0); + physicActor->setLastStuckPosition({0, 0, 0}); + } collisionObject->setWorldTransform(oldTransform); actor.mPosition = tempPosition; From e722c99e6284d5f3611b746e5e6cc562c28b8fa4 Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 21 Mar 2021 08:57:15 -0400 Subject: [PATCH 11/96] forgot to initialize these variables --- apps/openmw/mwphysics/actor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 06abe7240..905034cde 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -22,6 +22,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler) : mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false) , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents) + , mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) From 7bbbe40abe042c1931823d68f0d769f40a5f8695 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 21 Mar 2021 13:56:56 +0100 Subject: [PATCH 12/96] "static const" -> "static constexpr" in headers --- apps/opencs/model/prefs/shortcutsetting.hpp | 2 +- apps/openmw/mwgui/sortfilteritemmodel.hpp | 28 ++++++------- apps/openmw/mwgui/widgets.hpp | 2 +- apps/openmw/mwmechanics/creaturestats.hpp | 2 +- apps/openmw/mwmechanics/obstacle.hpp | 2 +- apps/openmw/mwphysics/constants.hpp | 20 ++++----- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwscript/ref.hpp | 4 +- apps/openmw/mwworld/containerstore.hpp | 36 ++++++++-------- apps/openmw/mwworld/inventorystore.hpp | 46 ++++++++++----------- 10 files changed, 72 insertions(+), 72 deletions(-) diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index a0c588b42..52298232e 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -34,7 +34,7 @@ namespace CSMPrefs void storeValue(const QKeySequence& sequence); void resetState(); - static const int MaxKeys = 4; + static constexpr int MaxKeys = 4; QPushButton* mButton; diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index fa70a0edd..64a01f71b 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -35,20 +35,20 @@ namespace MWGui bool onDropItem(const MWWorld::Ptr &item, int count) override; bool onTakeItem(const MWWorld::Ptr &item, int count) override; - static const int Category_Weapon = (1<<1); - static const int Category_Apparel = (1<<2); - static const int Category_Misc = (1<<3); - static const int Category_Magic = (1<<4); - static const int Category_All = 255; - - static const int Filter_OnlyIngredients = (1<<0); - static const int Filter_OnlyEnchanted = (1<<1); - static const int Filter_OnlyEnchantable = (1<<2); - static const int Filter_OnlyChargedSoulstones = (1<<3); - static const int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action - static const int Filter_OnlyRepairable = (1<<5); - static const int Filter_OnlyRechargable = (1<<6); - static const int Filter_OnlyRepairTools = (1<<7); + static constexpr int Category_Weapon = (1<<1); + static constexpr int Category_Apparel = (1<<2); + static constexpr int Category_Misc = (1<<3); + static constexpr int Category_Magic = (1<<4); + static constexpr int Category_All = 255; + + static constexpr int Filter_OnlyIngredients = (1<<0); + static constexpr int Filter_OnlyEnchanted = (1<<1); + static constexpr int Filter_OnlyEnchantable = (1<<2); + static constexpr int Filter_OnlyChargedSoulstones = (1<<3); + static constexpr int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action + static constexpr int Filter_OnlyRepairable = (1<<5); + static constexpr int Filter_OnlyRechargable = (1<<6); + static constexpr int Filter_OnlyRepairTools = (1<<7); private: diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 731a41a35..3c5528715 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -268,7 +268,7 @@ namespace MWGui void initialiseOverride() override; private: - static const int sIconOffset = 24; + static constexpr int sIconOffset = 24; void updateWidgets(); diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index b2c0aec98..e09f5197e 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -24,7 +24,7 @@ namespace MWMechanics { struct CorprusStats { - static const int sWorseningPeriod = 24; + static constexpr int sWorseningPeriod = 24; int mWorsenings[ESM::Attribute::Length]; MWWorld::TimeStamp mNextWorsening; diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 6c2197d81..b574bab67 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -12,7 +12,7 @@ namespace MWMechanics { struct Movement; - static const int NUM_EVADE_DIRECTIONS = 4; + static constexpr int NUM_EVADE_DIRECTIONS = 4; /// tests actor's proximity to a closed door by default bool proximityToDoor(const MWWorld::Ptr& actor, float minDist); diff --git a/apps/openmw/mwphysics/constants.hpp b/apps/openmw/mwphysics/constants.hpp index c6f2f3b53..d552ed49e 100644 --- a/apps/openmw/mwphysics/constants.hpp +++ b/apps/openmw/mwphysics/constants.hpp @@ -3,24 +3,24 @@ namespace MWPhysics { - static const float sStepSizeUp = 34.0f; - static const float sStepSizeDown = 62.0f; + static constexpr float sStepSizeUp = 34.0f; + static constexpr float sStepSizeDown = 62.0f; - static const float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes - static const float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes + static constexpr float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes + static constexpr float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes // whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance - static const bool sDoExtraStairHacks = true; + static constexpr bool sDoExtraStairHacks = true; - static const float sGroundOffset = 1.0f; - static const float sMaxSlope = 49.0f; + static constexpr float sGroundOffset = 1.0f; + static constexpr float sMaxSlope = 49.0f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. - static const int sMaxIterations = 8; + static constexpr int sMaxIterations = 8; // Allows for more precise movement solving without getting stuck or snagging too easily. - static const float sCollisionMargin = 0.1; + static constexpr float sCollisionMargin = 0.1; // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily // Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues - static const float sAllowedPenetration = 0.0; + static constexpr float sAllowedPenetration = 0.0; } #endif diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index ebfe8a2e5..04c5825c9 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -102,7 +102,7 @@ public: BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody }; /* This is the number of *discrete* blend masks. */ - static const size_t sNumBlendMasks = 4; + static constexpr size_t sNumBlendMasks = 4; /// Holds an animation priority value for each BoneGroup. struct AnimPriority diff --git a/apps/openmw/mwscript/ref.hpp b/apps/openmw/mwscript/ref.hpp index e572f5147..c52b419c1 100644 --- a/apps/openmw/mwscript/ref.hpp +++ b/apps/openmw/mwscript/ref.hpp @@ -14,7 +14,7 @@ namespace MWScript { struct ExplicitRef { - static const bool implicit = false; + static constexpr bool implicit = false; MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, bool activeOnly = false) const; @@ -22,7 +22,7 @@ namespace MWScript struct ImplicitRef { - static const bool implicit = true; + static constexpr bool implicit = true; MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, bool activeOnly = false) const; diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index e0843efba..882f5efc4 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -73,22 +73,22 @@ namespace MWWorld { public: - static const int Type_Potion = 0x0001; - static const int Type_Apparatus = 0x0002; - static const int Type_Armor = 0x0004; - static const int Type_Book = 0x0008; - static const int Type_Clothing = 0x0010; - static const int Type_Ingredient = 0x0020; - static const int Type_Light = 0x0040; - static const int Type_Lockpick = 0x0080; - static const int Type_Miscellaneous = 0x0100; - static const int Type_Probe = 0x0200; - static const int Type_Repair = 0x0400; - static const int Type_Weapon = 0x0800; - - static const int Type_Last = Type_Weapon; - - static const int Type_All = 0xffff; + static constexpr int Type_Potion = 0x0001; + static constexpr int Type_Apparatus = 0x0002; + static constexpr int Type_Armor = 0x0004; + static constexpr int Type_Book = 0x0008; + static constexpr int Type_Clothing = 0x0010; + static constexpr int Type_Ingredient = 0x0020; + static constexpr int Type_Light = 0x0040; + static constexpr int Type_Lockpick = 0x0080; + static constexpr int Type_Miscellaneous = 0x0100; + static constexpr int Type_Probe = 0x0200; + static constexpr int Type_Repair = 0x0400; + static constexpr int Type_Weapon = 0x0800; + + static constexpr int Type_Last = Type_Weapon; + + static constexpr int Type_All = 0xffff; static const std::string sGoldId; @@ -265,13 +265,13 @@ namespace MWWorld template struct IsConvertible { - static const bool value = true; + static constexpr bool value = true; }; template struct IsConvertible { - static const bool value = false; + static constexpr bool value = false; }; template diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index bfe0a9992..6809e63b2 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -42,29 +42,29 @@ namespace MWWorld { public: - static const int Slot_Helmet = 0; - static const int Slot_Cuirass = 1; - static const int Slot_Greaves = 2; - static const int Slot_LeftPauldron = 3; - static const int Slot_RightPauldron = 4; - static const int Slot_LeftGauntlet = 5; - static const int Slot_RightGauntlet = 6; - static const int Slot_Boots = 7; - static const int Slot_Shirt = 8; - static const int Slot_Pants = 9; - static const int Slot_Skirt = 10; - static const int Slot_Robe = 11; - static const int Slot_LeftRing = 12; - static const int Slot_RightRing = 13; - static const int Slot_Amulet = 14; - static const int Slot_Belt = 15; - static const int Slot_CarriedRight = 16; - static const int Slot_CarriedLeft = 17; - static const int Slot_Ammunition = 18; - - static const int Slots = 19; - - static const int Slot_NoSlot = -1; + static constexpr int Slot_Helmet = 0; + static constexpr int Slot_Cuirass = 1; + static constexpr int Slot_Greaves = 2; + static constexpr int Slot_LeftPauldron = 3; + static constexpr int Slot_RightPauldron = 4; + static constexpr int Slot_LeftGauntlet = 5; + static constexpr int Slot_RightGauntlet = 6; + static constexpr int Slot_Boots = 7; + static constexpr int Slot_Shirt = 8; + static constexpr int Slot_Pants = 9; + static constexpr int Slot_Skirt = 10; + static constexpr int Slot_Robe = 11; + static constexpr int Slot_LeftRing = 12; + static constexpr int Slot_RightRing = 13; + static constexpr int Slot_Amulet = 14; + static constexpr int Slot_Belt = 15; + static constexpr int Slot_CarriedRight = 16; + static constexpr int Slot_CarriedLeft = 17; + static constexpr int Slot_Ammunition = 18; + + static constexpr int Slots = 19; + + static constexpr int Slot_NoSlot = -1; private: From 209e7718a8e246a07e3afb8ffe40859f21df4594 Mon Sep 17 00:00:00 2001 From: "Hristos N. Triantafillou" Date: Sun, 21 Mar 2021 15:37:48 -0500 Subject: [PATCH 13/96] Clarify the requirements of a data folder The current text could be interpreted to mean that a data folder _must_ have a plugin, but this isn't the case. This added text clarifies that a plugin or resources are needed. --- docs/source/reference/modding/mod-install.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/reference/modding/mod-install.rst b/docs/source/reference/modding/mod-install.rst index 2c883aa59..c15d1b268 100644 --- a/docs/source/reference/modding/mod-install.rst +++ b/docs/source/reference/modding/mod-install.rst @@ -11,6 +11,7 @@ Install #. Locate the plugin files, ``.esp`` or ``.omwaddon``, or possibly ``.esm``. The folder containing the plugin files we will call your *data folder* #. Check that all resource folders (``Meshes``, ``Textures``, etc.) containing additional resource files (the actual meshes, textures, etc.) are in the *data folder*. + #. Note that not all mods have a plugin, and not all mods have resources, but they must at minimum have one or the other. .. note:: There may be multiple levels of folders, but the location of the plugins must be the same as the resource folders. From 63f01d8c5f3dcc86826acd36ca244a17e845201c Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 21 Mar 2021 20:45:46 +0000 Subject: [PATCH 14/96] Prevent physics death spiral by falling back to true delta time when needed --- CHANGELOG.md | 2 + apps/openmw/mwphysics/mtphysics.cpp | 77 ++++++++++++++++++++++++- apps/openmw/mwphysics/mtphysics.hpp | 13 ++++- apps/openmw/mwphysics/physicssystem.cpp | 13 ++--- apps/openmw/mwphysics/physicssystem.hpp | 2 +- components/misc/budgetmeasurement.hpp | 42 ++++++++++++++ 6 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 components/misc/budgetmeasurement.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 0891ecba9..4ac01c00f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ Bug #5424: Creatures do not headtrack player Bug #5425: Poison effect only appears for one frame Bug #5427: GetDistance unknown ID error is misleading + Bug #5431: Physics performance degradation after a specific number of actors on a scene Bug #5435: Enemies can't hurt the player when collision is off Bug #5441: Enemies can't push a player character when in critical strike stance Bug #5451: Magic projectiles don't disappear with the caster @@ -143,6 +144,7 @@ Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Feature #5813: Instanced groundcover support + Feature #5910: Fall back to delta time when physics can't keep up Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation Task #5844: Update 'toggle sneak' documentation diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 1fa6251f8..11eb7f909 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -101,7 +101,7 @@ namespace osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { - const float interpolationFactor = timeAccum / physicsDt; + const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f); return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); } @@ -138,7 +138,8 @@ namespace namespace MWPhysics { PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr collisionWorld) - : mPhysicsDt(physicsDt) + : mDefaultPhysicsDt(physicsDt) + , mPhysicsDt(physicsDt) , mTimeAccum(0.f) , mCollisionWorld(std::move(collisionWorld)) , mNumJobs(0) @@ -152,6 +153,11 @@ namespace MWPhysics , mNextLOS(0) , mFrameNumber(0) , mTimer(osg::Timer::instance()) + , mPrevStepCount(1) + , mBudget(physicsDt) + , mAsyncBudget(0.0f) + , mBudgetCursor(0) + , mAsyncStartTime(0) , mTimeBegin(0) , mTimeEnd(0) , mFrameStart(0) @@ -220,13 +226,61 @@ namespace MWPhysics thread.join(); } - const std::vector& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + std::tuple PhysicsTaskScheduler::calculateStepConfig(float timeAccum) const + { + int maxAllowedSteps = 2; + int numSteps = timeAccum / mDefaultPhysicsDt; + + // adjust maximum step count based on whether we're likely physics bottlenecked or not + // if maxAllowedSteps ends up higher than numSteps, we will not invoke delta time + // if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps that we expect to be within budget + // if it ends up lower than numSteps and also 1, we will run a single delta time physics step + // if we did not do this, and had a fixed step count limit, + // we would have an unnecessarily low render framerate if we were only physics bottlenecked, + // and we would be unnecessarily invoking true delta time if we were only render bottlenecked + + // get physics timing stats + float budgetMeasurement = std::max(mBudget.get(), mAsyncBudget.get()); + // time spent per step in terms of the intended physics framerate + budgetMeasurement /= mDefaultPhysicsDt; + // ensure sane minimum value + budgetMeasurement = std::max(0.00001f, budgetMeasurement); + // we're spending almost or more than realtime per physics frame; limit to a single step + if (budgetMeasurement > 0.95) + maxAllowedSteps = 1; + // physics is fairly cheap; limit based on expense + if (budgetMeasurement < 0.5) + maxAllowedSteps = std::ceil(1.0/budgetMeasurement); + // limit to a reasonable amount + maxAllowedSteps = std::min(10, maxAllowedSteps); + + // fall back to delta time for this frame if fixed timestep physics would fall behind + float actualDelta = mDefaultPhysicsDt; + if (numSteps > maxAllowedSteps) + { + numSteps = maxAllowedSteps; + // ensure that we do not simulate a frame ahead when doing delta time; this reduces stutter and latency + // this causes interpolation to 100% use the most recent physics result when true delta time is happening + // and we deliberately simulate up to exactly the timestamp that we want to render + actualDelta = timeAccum/float(numSteps+1); + // actually: if this results in a per-step delta less than the target physics steptime, clamp it + // this might reintroduce some stutter, but only comes into play in obscure cases + // (because numSteps is originally based on mDefaultPhysicsDt, this won't cause us to overrun) + actualDelta = std::max(actualDelta, mDefaultPhysicsDt); + } + + return std::make_tuple(numSteps, actualDelta); + } + + const std::vector& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. std::unique_lock lock(mSimulationMutex); + double timeStart = mTimer->tick(); + mMovedActors.clear(); // start by finishing previous background computation @@ -251,14 +305,21 @@ namespace MWPhysics mMovedActors.emplace_back(data.mActorRaw->getPtr()); } } + if(mAdvanceSimulation) + mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor); updateStats(frameStart, frameNumber, stats); } + auto [numSteps, newDelta] = calculateStepConfig(timeAccum); + timeAccum -= numSteps*newDelta; + // init for (auto& data : actorsData) data.updatePosition(); + mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; + mPhysicsDt = newDelta; mActorsFrameData = std::move(actorsData); mAdvanceSimulation = (mRemainingSteps != 0); mNewFrame = true; @@ -269,20 +330,30 @@ namespace MWPhysics if (mAdvanceSimulation) mWorldFrameData = std::make_unique(); + if (mAdvanceSimulation) + mBudgetCursor += 1; + if (mNumThreads == 0) { syncComputation(); + if(mAdvanceSimulation) + mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor); return mMovedActors; } + mAsyncStartTime = mTimer->tick(); lock.unlock(); mHasJob.notify_all(); + if (mAdvanceSimulation) + mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor); return mMovedActors; } const std::vector& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { std::unique_lock lock(mSimulationMutex); + mBudget.reset(mDefaultPhysicsDt); + mAsyncBudget.reset(0.0f); mMovedActors.clear(); mActorsFrameData.clear(); for (const auto& [_, actor] : actors) diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index b35ebd5ee..137755c21 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -13,6 +13,7 @@ #include "physicssystem.hpp" #include "ptrholder.hpp" +#include "components/misc/budgetmeasurement.hpp" namespace Misc { @@ -32,7 +33,7 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - const std::vector& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + const std::vector& moveActors(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); const std::vector& resetSimulation(const ActorMap& actors); @@ -58,11 +59,13 @@ namespace MWPhysics void updateAabbs(); void updatePtrAabb(const std::weak_ptr& ptr); void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + std::tuple calculateStepConfig(float timeAccum) const; std::unique_ptr mWorldFrameData; std::vector mActorsFrameData; std::vector mMovedActors; - const float mPhysicsDt; + float mDefaultPhysicsDt; + float mPhysicsDt; float mTimeAccum; std::shared_ptr mCollisionWorld; std::vector mLOSCache; @@ -94,6 +97,12 @@ namespace MWPhysics unsigned int mFrameNumber; const osg::Timer* mTimer; + + int mPrevStepCount; + Misc::BudgetMeasurement mBudget; + Misc::BudgetMeasurement mAsyncBudget; + unsigned int mBudgetCursor; + osg::Timer_t mAsyncStartTime; osg::Timer_t mTimeBegin; osg::Timer_t mTimeEnd; osg::Timer_t mFrameStart; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index dc9ab629a..65ed13f80 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -744,19 +744,14 @@ namespace MWPhysics { mTimeAccum += dt; - const int maxAllowedSteps = 20; - int numSteps = mTimeAccum / mPhysicsDt; - numSteps = std::min(numSteps, maxAllowedSteps); - - mTimeAccum -= numSteps * mPhysicsDt; - if (skipSimulation) return mTaskScheduler->resetSimulation(mActors); - return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats); + // modifies mTimeAccum + return mTaskScheduler->moveActors(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats); } - std::vector PhysicsSystem::prepareFrameData(int numSteps) + std::vector PhysicsSystem::prepareFrameData(bool willSimulate) { std::vector actorsFrameData; actorsFrameData.reserve(mMovementQueue.size()); @@ -796,7 +791,7 @@ namespace MWPhysics // Ue current value only if we don't advance the simulation. Otherwise we might get a stale value. MWWorld::Ptr standingOn; - if (numSteps == 0) + if (!willSimulate) standingOn = physicActor->getStandingOnPtr(); actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 80b2d98bc..354823986 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -252,7 +252,7 @@ namespace MWPhysics void updateWater(); - std::vector prepareFrameData(int numSteps); + std::vector prepareFrameData(bool willSimulate); osg::ref_ptr mUnrefQueue; diff --git a/components/misc/budgetmeasurement.hpp b/components/misc/budgetmeasurement.hpp new file mode 100644 index 000000000..3d56477af --- /dev/null +++ b/components/misc/budgetmeasurement.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H +#define OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H + + +namespace Misc +{ + +class BudgetMeasurement +{ + std::array mBudgetHistory; + std::array mBudgetStepCount; + +public: + BudgetMeasurement(const float default_expense) + { + mBudgetHistory = {default_expense, default_expense, default_expense, default_expense}; + mBudgetStepCount = {1, 1, 1, 1}; + } + + void reset(const float default_expense) + { + mBudgetHistory = {default_expense, default_expense, default_expense, default_expense}; + mBudgetStepCount = {1, 1, 1, 1}; + } + + void update(double delta, unsigned int stepCount, size_t cursor) + { + mBudgetHistory[cursor%4] = delta; + mBudgetStepCount[cursor%4] = stepCount; + } + + double get() const + { + float sum = (mBudgetHistory[0] + mBudgetHistory[1] + mBudgetHistory[2] + mBudgetHistory[3]); + unsigned int stepCountSum = (mBudgetStepCount[0] + mBudgetStepCount[1] + mBudgetStepCount[2] + mBudgetStepCount[3]); + return sum/float(stepCountSum); + } +}; + +} + +#endif From 4862e8c8f492548f9e8b3c4e0d1aa962179b0f2e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 21 Mar 2021 21:30:17 +0000 Subject: [PATCH 15/96] Bump aqt version --- CI/before_script.msvc.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index dba87128a..b2b6483ec 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -848,9 +848,11 @@ fi wrappedExit 1 fi - if ! [ -e "aqt-venv/${VENV_BIN_DIR}/aqt" ]; then + # check version + pip list | grep 'aqtinstall\s*1.1.3' + if [ $? -ne 0 ]; then echo " Installing aqt wheel into virtualenv..." - run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==0.9.2 + run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 fi popd > /dev/null From 2fdbe9b3f6a5b47ae692148dd0a8651c2836d5f6 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Thu, 17 Dec 2020 00:46:09 +0300 Subject: [PATCH 16/96] Handle BSShader[PP/No]LightingProperty --- components/nif/niffile.cpp | 2 + components/nif/property.cpp | 32 ++++- components/nif/property.hpp | 42 ++++++- components/nif/record.hpp | 4 +- components/nifosg/nifloader.cpp | 139 ++++++++++++++++++++++ components/resource/scenemanager.cpp | 2 +- components/shader/shadervisitor.cpp | 17 ++- components/shader/shadervisitor.hpp | 5 +- files/shaders/CMakeLists.txt | 4 + files/shaders/nv_default_fragment.glsl | 106 +++++++++++++++++ files/shaders/nv_default_vertex.glsl | 58 +++++++++ files/shaders/nv_nolighting_fragment.glsl | 55 +++++++++ files/shaders/nv_nolighting_vertex.glsl | 49 ++++++++ files/shaders/objects_fragment.glsl | 5 +- files/shaders/objects_vertex.glsl | 4 +- 15 files changed, 509 insertions(+), 15 deletions(-) create mode 100644 files/shaders/nv_default_fragment.glsl create mode 100644 files/shaders/nv_default_vertex.glsl create mode 100644 files/shaders/nv_nolighting_fragment.glsl create mode 100644 files/shaders/nv_nolighting_vertex.glsl diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 665533c91..3e226b35e 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -133,6 +133,8 @@ static std::map makeFactory() factory["BSShaderTextureSet"] = {&construct , RC_BSShaderTextureSet }; factory["BSLODTriShape"] = {&construct , RC_BSLODTriShape }; factory["BSShaderProperty"] = {&construct , RC_BSShaderProperty }; + factory["BSShaderPPLightingProperty"] = {&construct , RC_BSShaderPPLightingProperty }; + factory["BSShaderNoLightingProperty"] = {&construct , RC_BSShaderNoLightingProperty }; return factory; } diff --git a/components/nif/property.cpp b/components/nif/property.cpp index d5357e123..1d2dd885d 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -118,6 +118,34 @@ void BSShaderLightingProperty::read(NIFStream *nif) clamp = nif->getUInt(); } +void BSShaderPPLightingProperty::read(NIFStream *nif) +{ + BSShaderLightingProperty::read(nif); + textureSet.read(nif); + if (nif->getBethVersion() <= 14) + return; + refraction.strength = nif->getFloat(); + refraction.period = nif->getInt(); + if (nif->getBethVersion() <= 24) + return; + parallax.passes = nif->getFloat(); + parallax.scale = nif->getFloat(); +} + +void BSShaderPPLightingProperty::post(NIFFile *nif) +{ + BSShaderLightingProperty::post(nif); + textureSet.post(nif); +} + +void BSShaderNoLightingProperty::read(NIFStream *nif) +{ + BSShaderLightingProperty::read(nif); + filename = nif->getSizedString(); + if (nif->getBethVersion() >= 27) + falloffParams = nif->getVector4(); +} + void NiFogProperty::read(NIFStream *nif) { Property::read(nif); @@ -137,8 +165,8 @@ void S_MaterialProperty::read(NIFStream *nif) emissive = nif->getVector3(); glossiness = nif->getFloat(); alpha = nif->getFloat(); - if (nif->getBethVersion() > 21) - emissive *= nif->getFloat(); + if (nif->getBethVersion() >= 22) + emissiveMult = nif->getFloat(); } void S_VertexColorProperty::read(NIFStream *nif) diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 008e84515..9c76f6d6e 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -118,6 +118,18 @@ struct NiShadeProperty : public Property struct BSShaderProperty : public NiShadeProperty { + enum BSShaderType + { + SHADER_TALL_GRASS = 0, + SHADER_DEFAULT = 1, + SHADER_SKY = 10, + SHADER_SKIN = 14, + SHADER_WATER = 17, + SHADER_LIGHTING30 = 29, + SHADER_TILE = 32, + SHADER_NOLIGHTING = 33 + }; + unsigned int type{0u}, flags1{0u}, flags2{0u}; float envMapIntensity{0.f}; void read(NIFStream *nif) override; @@ -129,6 +141,34 @@ struct BSShaderLightingProperty : public BSShaderProperty void read(NIFStream *nif) override; }; +struct BSShaderPPLightingProperty : public BSShaderLightingProperty +{ + BSShaderTextureSetPtr textureSet; + struct RefractionSettings + { + float strength{0.f}; + int period{0}; + }; + struct ParallaxSettings + { + float passes{0.f}; + float scale{0.f}; + }; + RefractionSettings refraction; + ParallaxSettings parallax; + + void read(NIFStream *nif) override; + void post(NIFFile *nif) override; +}; + +struct BSShaderNoLightingProperty : public BSShaderLightingProperty +{ + std::string filename; + osg::Vec4f falloffParams; + + void read(NIFStream *nif) override; +}; + struct NiDitherProperty : public Property { unsigned short flags; @@ -193,7 +233,7 @@ struct S_MaterialProperty // The vector components are R,G,B osg::Vec3f ambient{1.f,1.f,1.f}, diffuse{1.f,1.f,1.f}; osg::Vec3f specular, emissive; - float glossiness{0.f}, alpha{0.f}; + float glossiness{0.f}, alpha{0.f}, emissiveMult{1.f}; void read(NIFStream *nif); }; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index efacd8246..ed97acabc 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -122,7 +122,9 @@ enum RecordType RC_NiColorInterpolator, RC_BSShaderTextureSet, RC_BSLODTriShape, - RC_BSShaderProperty + RC_BSShaderProperty, + RC_BSShaderPPLightingProperty, + RC_BSShaderNoLightingProperty }; /// Base class for all records diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3e34969f4..ae1726f06 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1672,6 +1672,85 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } + void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager, std::vector& boundTextures) + { + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + + const unsigned int uvSet = 0; + + for (size_t i = 0; i < textureSet->textures.size(); ++i) + { + if (textureSet->textures[i].empty()) + continue; + switch(i) + { + case Nif::BSShaderTextureSet::TextureType_Base: + case Nif::BSShaderTextureSet::TextureType_Normal: + case Nif::BSShaderTextureSet::TextureType_Glow: + break; + default: + { + Log(Debug::Info) << "Unhandled texture stage " << i << " on shape \"" << nodeName << "\" in " << mFilename; + continue; + } + } + std::string filename = Misc::ResourceHelpers::correctTexturePath(textureSet->textures[i], imageManager->getVFS()); + osg::ref_ptr image = imageManager->getImage(filename); + osg::ref_ptr texture2d = new osg::Texture2D(image); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + bool wrapT = clamp & 0x1; + bool wrapS = (clamp >> 1) & 0x1; + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + unsigned int texUnit = boundTextures.size(); + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + // BSShaderTextureSet presence means there's no need for FFP support for the affected node + switch (i) + { + case Nif::BSShaderTextureSet::TextureType_Base: + texture2d->setName("diffuseMap"); + break; + case Nif::BSShaderTextureSet::TextureType_Normal: + texture2d->setName("normalMap"); + break; + case Nif::BSShaderTextureSet::TextureType_Glow: + texture2d->setName("emissiveMap"); + break; + } + boundTextures.emplace_back(uvSet); + } + } + + const std::string& getNVShaderPrefix(unsigned int type) const + { + static const std::map mapping = + { + {Nif::BSShaderProperty::SHADER_TALL_GRASS, std::string()}, + {Nif::BSShaderProperty::SHADER_DEFAULT, "nv_default"}, + {Nif::BSShaderProperty::SHADER_SKY, std::string()}, + {Nif::BSShaderProperty::SHADER_SKIN, std::string()}, + {Nif::BSShaderProperty::SHADER_WATER, std::string()}, + {Nif::BSShaderProperty::SHADER_LIGHTING30, std::string()}, + {Nif::BSShaderProperty::SHADER_TILE, std::string()}, + {Nif::BSShaderProperty::SHADER_NOLIGHTING, "nv_nolighting"}, + }; + auto prefix = mapping.find(type); + if (prefix == mapping.end()) + Log(Debug::Warning) << "Unknown shader type " << type << " in " << mFilename; + else if (prefix->second.empty()) + Log(Debug::Warning) << "Unhandled shader type " << type << " in " << mFilename; + else + return prefix->second; + + return mapping.at(Nif::BSShaderProperty::SHADER_DEFAULT); + } + void handleProperty(const Nif::Property *property, osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { @@ -1757,6 +1836,63 @@ namespace NifOsg handleTextureProperty(texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags); break; } + case Nif::RC_BSShaderPPLightingProperty: + { + auto texprop = static_cast(property); + bool shaderRequired = true; + node->setUserValue("shaderPrefix", getNVShaderPrefix(texprop->type)); + node->setUserValue("shaderRequired", shaderRequired); + osg::StateSet* stateset = node->getOrCreateStateSet(); + if (!texprop->textureSet.empty()) + { + auto textureSet = texprop->textureSet.getPtr(); + handleTextureSet(textureSet, texprop->clamp, node->getName(), stateset, imageManager, boundTextures); + } + handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + break; + } + case Nif::RC_BSShaderNoLightingProperty: + { + auto texprop = static_cast(property); + bool shaderRequired = true; + node->setUserValue("shaderPrefix", getNVShaderPrefix(texprop->type)); + node->setUserValue("shaderRequired", shaderRequired); + osg::StateSet* stateset = node->getOrCreateStateSet(); + if (!texprop->filename.empty()) + { + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + std::string filename = Misc::ResourceHelpers::correctTexturePath(texprop->filename, imageManager->getVFS()); + osg::ref_ptr image = imageManager->getImage(filename); + osg::ref_ptr texture2d = new osg::Texture2D(image); + texture2d->setName("diffuseMap"); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + bool wrapT = texprop->clamp & 0x1; + bool wrapS = (texprop->clamp >> 1) & 0x1; + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + const unsigned int texUnit = 0; + const unsigned int uvSet = 0; + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + boundTextures.push_back(uvSet); + } + if (mBethVersion >= 27) + { + stateset->addUniform(new osg::Uniform("useFalloff", true)); + stateset->addUniform(new osg::Uniform("falloffParams", texprop->falloffParams)); + } + else + { + stateset->addUniform(new osg::Uniform("useFalloff", false)); + } + handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + break; + } // unused by mw case Nif::RC_NiShadeProperty: case Nif::RC_NiDitherProperty: @@ -1809,6 +1945,7 @@ namespace NifOsg bool hasMatCtrl = false; int lightmode = 1; + float emissiveMult = 1.f; for (const Nif::Property* property : properties) { @@ -1828,6 +1965,7 @@ namespace NifOsg mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.diffuse, matprop->data.alpha)); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.ambient, 1.f)); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.emissive, 1.f)); + emissiveMult = matprop->data.emissiveMult; mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.specular, 1.f)); mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->data.glossiness); @@ -1948,6 +2086,7 @@ namespace NifOsg mat = shareAttribute(mat); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); } }; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e46ce2016..66d48f971 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -774,7 +774,7 @@ namespace Resource Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix, bool translucentFramebuffer) { - Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix+"_vertex.glsl", shaderPrefix+"_fragment.glsl"); + Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix); shaderVisitor->setForceShaders(mForceShaders); shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps); shaderVisitor->setNormalMapPattern(mNormalMapPattern); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index eae9ad2db..1af9f94c5 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -42,7 +42,7 @@ namespace Shader } - ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) + ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultShaderPrefix) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mForceShaders(false) , mAllowedToModifyStateSets(true) @@ -52,8 +52,7 @@ namespace Shader , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) - , mDefaultVsTemplate(defaultVsTemplate) - , mDefaultFsTemplate(defaultFsTemplate) + , mDefaultShaderPrefix(defaultShaderPrefix) { mRequirements.emplace_back(); } @@ -129,6 +128,10 @@ namespace Shader if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); + bool shaderRequired = false; + if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired) + mRequirements.back().mShaderRequired = true; + if (!texAttributes.empty()) { const osg::Texture* diffuseMap = nullptr; @@ -440,8 +443,12 @@ namespace Shader defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; - osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); - osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); + std::string shaderPrefix; + if (!node.getUserValue("shaderPrefix", shaderPrefix)) + shaderPrefix = mDefaultShaderPrefix; + + osg::ref_ptr vertexShader (mShaderManager.getShader(shaderPrefix + "_vertex.glsl", defineMap, osg::Shader::VERTEX)); + osg::ref_ptr fragmentShader (mShaderManager.getShader(shaderPrefix + "_fragment.glsl", defineMap, osg::Shader::FRAGMENT)); if (vertexShader && fragmentShader) { diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index f7c6f8312..30ff41a33 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -17,7 +17,7 @@ namespace Shader class ShaderVisitor : public osg::NodeVisitor { public: - ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); + ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultShaderPrefix); /// By default, only bump mapped objects will have a shader added to them. /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. @@ -104,8 +104,7 @@ namespace Shader }; std::vector mRequirements; - std::string mDefaultVsTemplate; - std::string mDefaultFsTemplate; + std::string mDefaultShaderPrefix; void createProgram(const ShaderRequirements& reqs); void ensureFFP(osg::Node& node); diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 7250aa372..e06dfe56a 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -26,6 +26,10 @@ set(SHADER_FILES shadowcasting_vertex.glsl shadowcasting_fragment.glsl vertexcolors.glsl + nv_default_vertex.glsl + nv_default_fragment.glsl + nv_nolighting_vertex.glsl + nv_nolighting_fragment.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl new file mode 100644 index 000000000..03fa378a6 --- /dev/null +++ b/files/shaders/nv_default_fragment.glsl @@ -0,0 +1,106 @@ +#version 120 + +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + +#if @diffuseMap +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; +#endif + +#if @emissiveMap +uniform sampler2D emissiveMap; +varying vec2 emissiveMapUV; +#endif + +#if @normalMap +uniform sampler2D normalMap; +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +uniform bool noAlpha; + +varying float euclideanDepth; +varying float linearDepth; + +#define PER_PIXEL_LIGHTING 1 + +varying vec3 passViewPos; +varying vec3 passNormal; + +#include "vertexcolors.glsl" +#include "shadows_fragment.glsl" +#include "lighting.glsl" +#include "alpha.glsl" + +uniform float emissiveMult; + +void main() +{ +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); +#else + gl_FragData[0] = vec4(1.0); +#endif + + vec4 diffuseColor = getDiffuseColor(); + gl_FragData[0].a *= diffuseColor.a; + alphaTest(); + +#if @normalMap + vec4 normalTex = texture2D(normalMap, normalMapUV); + // Must flip Y for DirectX format normal maps + normalTex.y = 1.0 - normalTex.y; + + vec3 normalizedNormal = normalize(passNormal); + vec3 normalizedTangent = normalize(passTangent.xyz); + vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; + mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); + + vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); +#else + vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); +#endif + + float shadowing = unshadowedLightRatio(linearDepth); + vec3 diffuseLight, ambientLight; + doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); + vec3 emission = getEmissionColor().xyz * emissiveMult; +#if @emissiveMap + emission *= texture2D(emissiveMap, emissiveMapUV).xyz; +#endif + vec3 lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; + +#if @clamp + lighting = clamp(lighting, vec3(0.0), vec3(1.0)); +#else + lighting = max(lighting, 0.0); +#endif + + gl_FragData[0].xyz *= lighting; + + float shininess = gl_FrontMaterial.shininess; + vec3 matSpec = getSpecularColor().xyz; +#if @normalMap + matSpec *= normalTex.a; +#endif + + if (matSpec != vec3(0.0)) + gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; +#if @radialFog + float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#else + float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#endif + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + +#if @translucentFramebuffer + if (noAlpha) + gl_FragData[0].a = 1.0; +#endif + + applyShadowDebugOverlay(); +} diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl new file mode 100644 index 000000000..7c9d434f1 --- /dev/null +++ b/files/shaders/nv_default_vertex.glsl @@ -0,0 +1,58 @@ +#version 120 + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +#if @emissiveMap +varying vec2 emissiveMapUV; +#endif + +#if @normalMap +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +varying float euclideanDepth; +varying float linearDepth; + +varying vec3 passViewPos; +varying vec3 passNormal; + +#define PER_PIXEL_LIGHTING 1 + +#include "vertexcolors.glsl" +#include "shadows_vertex.glsl" +#include "lighting.glsl" + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + + vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + gl_ClipVertex = viewPos; + euclideanDepth = length(viewPos.xyz); + linearDepth = gl_Position.z; + +#if @diffuseMap + diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; +#endif + +#if @emissiveMap + emissiveMapUV = (gl_TextureMatrix[@emissiveMapUV] * gl_MultiTexCoord@emissiveMapUV).xy; +#endif + +#if @normalMap + normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; + passTangent = gl_MultiTexCoord7.xyzw; +#endif + + passColor = gl_Color; + passViewPos = viewPos.xyz; + passNormal = gl_Normal.xyz; + +#if @shadows_enabled + vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + setupShadowCoords(viewPos, viewNormal); +#endif +} diff --git a/files/shaders/nv_nolighting_fragment.glsl b/files/shaders/nv_nolighting_fragment.glsl new file mode 100644 index 000000000..27679a069 --- /dev/null +++ b/files/shaders/nv_nolighting_fragment.glsl @@ -0,0 +1,55 @@ +#version 120 + +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + +#if @diffuseMap +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; +#endif + +uniform bool noAlpha; + +#if @radialFog +varying float euclideanDepth; +#else +varying float linearDepth; +#endif + +uniform bool useFalloff; + +varying float passFalloff; + +#include "vertexcolors.glsl" +#include "alpha.glsl" + +void main() +{ +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV); +#else + gl_FragData[0] = vec4(1.0); +#endif + + gl_FragData[0] *= getDiffuseColor(); + + if (useFalloff) + gl_FragData[0].a *= passFalloff; + + alphaTest(); + +#if @radialFog + float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#else + float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#endif + +#if @translucentFramebuffer + if (noAlpha) + gl_FragData[0].a = 1.0; +#endif + + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +} diff --git a/files/shaders/nv_nolighting_vertex.glsl b/files/shaders/nv_nolighting_vertex.glsl new file mode 100644 index 000000000..275f1e573 --- /dev/null +++ b/files/shaders/nv_nolighting_vertex.glsl @@ -0,0 +1,49 @@ +#version 120 + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +#if @radialFog +varying float euclideanDepth; +#else +varying float linearDepth; +#endif + +uniform bool useFalloff; +uniform vec4 falloffParams; + +varying vec3 passViewPos; +varying float passFalloff; + +#include "vertexcolors.glsl" + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + + vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + gl_ClipVertex = viewPos; +#if @radialFog + euclideanDepth = length(viewPos.xyz); +#else + linearDepth = gl_Position.z; +#endif + +#if @diffuseMap + diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; +#endif + + passColor = gl_Color; + if (useFalloff) + { + vec3 viewNormal = gl_NormalMatrix * normalize(gl_Normal.xyz); + vec3 viewDir = normalize(viewPos.xyz); + float viewAngle = abs(dot(viewNormal, viewDir)); + passFalloff = smoothstep(falloffParams.y, falloffParams.x, viewAngle); + } + else + { + passFalloff = 1.0; + } +} diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index e0d7833c9..6b67be937 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -64,6 +64,8 @@ varying float linearDepth; #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; centroid varying vec3 shadowDiffuseLighting; +#else +uniform float emissiveMult; #endif varying vec3 passViewPos; varying vec3 passNormal; @@ -168,7 +170,8 @@ void main() #else vec3 diffuseLight, ambientLight; doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); - lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + vec3 emission = getEmissionColor().xyz * emissiveMult; + lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; #endif #if @clamp diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 15467933b..bf5bdb40c 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -45,6 +45,7 @@ varying float linearDepth; #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; centroid varying vec3 shadowDiffuseLighting; +uniform float emissiveMult; #endif varying vec3 passViewPos; varying vec3 passNormal; @@ -114,7 +115,8 @@ void main(void) #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); - passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + vec3 emission = getEmissionColor().xyz * emissiveMult; + passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; shadowDiffuseLighting *= getDiffuseColor().xyz; #endif From 580fa78034faaa6b51ca0ae6fdd631ef2e0ef69f Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 22 Mar 2021 20:43:34 +0100 Subject: [PATCH 17/96] Don't purge summon effects with invalid creature ids --- apps/openmw/mwmechanics/summoning.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 0f699ccad..9f65f3d6c 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -145,6 +145,12 @@ namespace MWMechanics for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) { + if(it->second == -1) + { + // Keep the spell effect active if we failed to spawn anything + it++; + continue; + } MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished())) { From 8e9bd5c0bd78da03ed9ae8c4fb1145c306afc2c5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 22 Mar 2021 20:44:13 +0100 Subject: [PATCH 18/96] Don't throw an exception when equipping a bound item fails --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/actors.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac01c00f..6c36ce3eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ Bug #5899: Visible modal windows and dropdowns crashing game on exit Bug #5902: NiZBufferProperty is unable to disable the depth test Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs + Bug #5912: ImprovedBound mod doesn't work Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c0a137158..a1e2d2a72 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -346,7 +346,11 @@ namespace MWMechanics if (actor != MWMechanics::getPlayer()) return; - MWWorld::Ptr newItem = *store.getSlot(slot); + MWWorld::Ptr newItem; + auto it = store.getSlot(slot); + // Equip can fail because beast races cannot equip boots/helmets + if(it != store.end()) + newItem = *it; if (newItem.isEmpty() || boundPtr != newItem) return; From 5e1960a76a3e8dcf63a1f12e654020364a8d3236 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 22 Mar 2021 22:29:10 +0100 Subject: [PATCH 19/96] Disallow inserting containers, creatures, and npcs from the save game not present in content files --- apps/openmw/mwworld/esmstore.cpp | 6 ++++-- apps/openmw/mwworld/store.cpp | 12 +++++++++--- apps/openmw/mwworld/store.hpp | 6 +++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 90bc80b48..5fa468428 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -384,12 +384,14 @@ void ESMStore::validate() case ESM::REC_ENCH: case ESM::REC_SPEL: case ESM::REC_WEAP: - case ESM::REC_NPC_: case ESM::REC_LEVI: case ESM::REC_LEVC: + mStores[type]->read (reader); + return true; + case ESM::REC_NPC_: case ESM::REC_CREA: case ESM::REC_CONT: - mStores[type]->read (reader); + mStores[type]->read (reader, true); return true; case ESM::REC_DYNA: diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 69ca1f8c2..9d6552106 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -250,9 +250,15 @@ namespace MWWorld } } template - T *Store::insert(const T &item) + T *Store::insert(const T &item, bool overrideOnly) { std::string id = Misc::StringUtils::lowerCase(item.mId); + if(overrideOnly) + { + auto it = mStatic.find(id); + if(it == mStatic.end()) + return nullptr; + } std::pair result = mDynamic.insert(std::pair(id, item)); T *ptr = &result.first->second; @@ -337,13 +343,13 @@ namespace MWWorld } } template - RecordId Store::read(ESM::ESMReader& reader) + RecordId Store::read(ESM::ESMReader& reader, bool overrideOnly) { T record; bool isDeleted = false; record.load (reader, isDeleted); - insert (record); + insert (record, overrideOnly); return RecordId(record.mId, isDeleted); } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index cb9f4f1e0..9cb1c7473 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -46,7 +46,7 @@ namespace MWWorld virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - virtual RecordId read (ESM::ESMReader& reader) { return RecordId(); } + virtual RecordId read (ESM::ESMReader& reader, bool overrideOnly = false) { return RecordId(); } ///< Read into dynamic storage }; @@ -192,7 +192,7 @@ namespace MWWorld /// @note The record identifiers are listed in the order that the records were defined by the content files. void listIdentifier(std::vector &list) const override; - T *insert(const T &item); + T *insert(const T &item, bool overrideOnly = false); T *insertStatic(const T &item); bool eraseStatic(const std::string &id) override; @@ -201,7 +201,7 @@ namespace MWWorld RecordId load(ESM::ESMReader &esm) override; void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override; - RecordId read(ESM::ESMReader& reader) override; + RecordId read(ESM::ESMReader& reader, bool overrideOnly = false) override; }; template <> From e79036f4e008b17c1b6c78ff0055951ede736e22 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 23 Mar 2021 20:43:52 +0100 Subject: [PATCH 20/96] Don't erase the player --- apps/openmw/mwworld/esmstore.hpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d69c56d8c..4d962b28c 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -80,8 +80,6 @@ namespace MWWorld std::map mStores; - ESM::NPC mPlayerTemplate; - unsigned int mDynamicCount; mutable std::map > mSpellListCache; @@ -172,14 +170,13 @@ namespace MWWorld for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) it->second->clearDynamic(); - mNpcs.insert(mPlayerTemplate); + movePlayerRecord(); } void movePlayerRecord () { - mPlayerTemplate = *mNpcs.find("player"); - mNpcs.eraseStatic(mPlayerTemplate.mId); - mNpcs.insert(mPlayerTemplate); + auto player = mNpcs.find("player"); + mNpcs.insert(*player); } void load(ESM::ESMReader &esm, Loading::Listener* listener); From cf5a93d7121412d6487f011451c873d07b83036b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 23 Mar 2021 21:07:57 +0100 Subject: [PATCH 21/96] Also run NPC validation for modified base records --- apps/openmw/mwbase/world.hpp | 2 + apps/openmw/mwstate/statemanagerimp.cpp | 1 + apps/openmw/mwworld/esmstore.cpp | 103 ++++++++++++++---------- apps/openmw/mwworld/esmstore.hpp | 3 + apps/openmw/mwworld/worldimp.cpp | 5 ++ apps/openmw/mwworld/worldimp.hpp | 2 + 6 files changed, 73 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 27980f070..5e844ffae 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -443,6 +443,8 @@ namespace MWBase virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; virtual void disableDeferredPreviewRotation() = 0; + virtual void saveLoaded() = 0; + virtual void setupPlayer() = 0; virtual void renderPlayer() = 0; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index f605344bf..93f75c6f1 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -505,6 +505,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str character->getPath().filename().string()); MWBase::Environment::get().getWindowManager()->setNewGame(false); + MWBase::Environment::get().getWorld()->saveLoaded(); MWBase::Environment::get().getWorld()->setupPlayer(); MWBase::Environment::get().getWorld()->renderPlayer(); MWBase::Environment::get().getWindowManager()->updatePlayer(); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 5fa468428..986c4f858 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -48,6 +48,57 @@ namespace } } } + + std::vector getNPCsToReplace(const MWWorld::Store& factions, const MWWorld::Store& classes, const std::map& npcs) + { + // Cache first class from store - we will use it if current class is not found + std::string defaultCls; + auto it = classes.begin(); + if (it != classes.end()) + defaultCls = it->mId; + else + throw std::runtime_error("List of NPC classes is empty!"); + + // Validate NPCs for non-existing class and faction. + // We will replace invalid entries by fixed ones + std::vector npcsToReplace; + + for (auto it : npcs) + { + ESM::NPC npc = it.second; + bool changed = false; + + const std::string npcFaction = npc.mFaction; + if (!npcFaction.empty()) + { + const ESM::Faction *fact = factions.search(npcFaction); + if (!fact) + { + Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it."; + npc.mFaction.clear(); + npc.mNpdt.mRank = 0; + changed = true; + } + } + + std::string npcClass = npc.mClass; + if (!npcClass.empty()) + { + const ESM::Class *cls = classes.search(npcClass); + if (!cls) + { + Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement."; + npc.mClass = defaultCls; + changed = true; + } + } + + if (changed) + npcsToReplace.push_back(npc); + } + + return npcsToReplace; + } } namespace MWWorld @@ -218,49 +269,7 @@ int ESMStore::getRefCount(const std::string& id) const void ESMStore::validate() { - // Cache first class from store - we will use it if current class is not found - std::string defaultCls = ""; - Store::iterator it = mClasses.begin(); - if (it != mClasses.end()) - defaultCls = it->mId; - else - throw std::runtime_error("List of NPC classes is empty!"); - - // Validate NPCs for non-existing class and faction. - // We will replace invalid entries by fixed ones - std::vector npcsToReplace; - for (ESM::NPC npc : mNpcs) - { - bool changed = false; - - const std::string npcFaction = npc.mFaction; - if (!npcFaction.empty()) - { - const ESM::Faction *fact = mFactions.search(npcFaction); - if (!fact) - { - Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it."; - npc.mFaction.clear(); - npc.mNpdt.mRank = 0; - changed = true; - } - } - - std::string npcClass = npc.mClass; - if (!npcClass.empty()) - { - const ESM::Class *cls = mClasses.search(npcClass); - if (!cls) - { - Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement."; - npc.mClass = defaultCls; - changed = true; - } - } - - if (changed) - npcsToReplace.push_back(npc); - } + std::vector npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mStatic); for (const ESM::NPC &npc : npcsToReplace) { @@ -331,6 +340,14 @@ void ESMStore::validate() } } +void ESMStore::validateDynamic() +{ + std::vector npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mDynamic); + + for (const ESM::NPC &npc : npcsToReplace) + mNpcs.insert(npc); +} + int ESMStore::countSavedGameRecords() const { return 1 // DYNA (dynamic name counter) diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 4d962b28c..26f497a52 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -179,6 +179,9 @@ namespace MWWorld mNpcs.insert(*player); } + /// Validate entries in store after loading a save + void validateDynamic(); + void load(ESM::ESMReader &esm, Loading::Listener* listener); template diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 01b90e907..ce09cf112 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2473,6 +2473,11 @@ namespace MWWorld mRendering->getCamera()->adjustCameraDistance(dist); } + void World::saveLoaded() + { + mStore.validateDynamic(); + } + void World::setupPlayer() { const ESM::NPC *player = mStore.get().find("player"); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 23153a31c..e2d344930 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -549,6 +549,8 @@ namespace MWWorld void applyDeferredPreviewRotationToPlayer(float dt) override; void disableDeferredPreviewRotation() override; + void saveLoaded() override; + void setupPlayer() override; void renderPlayer() override; From 313355cb3d72ba2ddd4b1557d801a0852151bf2f Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 23 Mar 2021 21:40:32 +0100 Subject: [PATCH 22/96] Fix default max tile size in navigator doc --- docs/source/reference/modding/settings/navigator.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index af40ac750..fcef549d0 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -305,7 +305,7 @@ tile size :Type: integer :Range: > 0 -:Default: 64 +:Default: 128 The width and height of each tile. From 39c0ce9ddf5d1c85c2e4cac4eb6b1f6ad980f2d0 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 23 Mar 2021 23:15:13 +0100 Subject: [PATCH 23/96] Build limited path for far destinations When distance between start and end point is greater than max radius of area possibly covered by navmesh there is no way to find path via navmesh. Also if distance is greater than cell size navmesh might not exists withing mentioned area because cell is not loaded therefore navmesh is not generated. So minumum of these values is used to limit max path distance. Assuming that path actually exists it's possible to build path to the edge of a circle. When actor reaches initial edge path is built further. However it will not be optimal. --- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 17 +++++++++++++++++ apps/openmw/mwmechanics/pathfinding.hpp | 4 ++++ components/detournavigator/navigator.hpp | 2 ++ components/detournavigator/navigatorimpl.cpp | 6 ++++++ components/detournavigator/navigatorimpl.hpp | 2 ++ components/detournavigator/navigatorstub.hpp | 5 +++++ components/detournavigator/settingsutils.hpp | 12 +++++++++++- 8 files changed, 48 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 00e1c9c3a..214aad320 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -114,7 +114,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (wasShortcutting || doesPathNeedRecalc(dest, actor)) // if need to rebuild path { const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor); - mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), + mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); mRotateOnTheRunChecks = 3; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 93ae90547..781b897a7 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -442,4 +442,21 @@ namespace MWMechanics std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath)); } + + void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) + { + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + const auto maxDistance = std::min( + navigator->getMaxNavmeshAreaRealRadius(), + static_cast(Constants::CellSizeInUnits) + ); + const auto startToEnd = endPoint - startPoint; + const auto distance = startToEnd.length(); + if (distance <= maxDistance) + return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, halfExtents, flags, areaCosts); + const auto end = startPoint + startToEnd * maxDistance / distance; + buildPath(actor, startPoint, end, cell, pathgridGraph, halfExtents, flags, areaCosts); + } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index b5c376b8c..ed88a57ca 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -101,6 +101,10 @@ namespace MWMechanics void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); + void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); + /// Remove front point if exist and within tolerance void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight, bool canMoveByZ); diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index ef61f78c6..d08bfa640 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -235,6 +235,8 @@ namespace DetourNavigator const osg::Vec3f& end, const Flags includeFlags) const; virtual RecastMeshTiles getRecastMeshTiles() = 0; + + virtual float getMaxNavmeshAreaRealRadius() const = 0; }; } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 142ba590d..abfb20ba8 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -213,4 +213,10 @@ namespace DetourNavigator ++it; } } + + float NavigatorImpl::getMaxNavmeshAreaRealRadius() const + { + const auto& settings = getSettings(); + return getRealTileSize(settings) * getMaxNavmeshAreaRadius(settings); + } } diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index e197c71b7..74fff0dea 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -60,6 +60,8 @@ namespace DetourNavigator RecastMeshTiles getRecastMeshTiles() override; + float getMaxNavmeshAreaRealRadius() const override; + private: Settings mSettings; NavMeshManager mNavMeshManager; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index f1f9e06ef..f6892bf1b 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -92,6 +92,11 @@ namespace DetourNavigator return {}; } + float getMaxNavmeshAreaRealRadius() const override + { + return std::numeric_limits::max(); + } + private: Settings mDefaultSettings {}; SharedNavMeshCacheItem mEmptyNavMeshCacheItem; diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 39ffc03d1..8f1c96e28 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -1,4 +1,4 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H #include "settings.hpp" @@ -89,6 +89,16 @@ namespace DetourNavigator transform.getOrigin() + btVector3(0, 0, getSwimLevel(settings, agentHalfExtentsZ) - agentHalfExtentsZ) ); } + + inline float getRealTileSize(const Settings& settings) + { + return settings.mTileSize * settings.mCellSize / settings.mRecastScaleFactor; + } + + inline float getMaxNavmeshAreaRadius(const Settings& settings) + { + return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1; + } } #endif From f32e1790bcdf1b85e655d0a8ae34a45aa463ed4d Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 23 Mar 2021 23:48:11 +0100 Subject: [PATCH 24/96] Add half extents to AiEscord max distance For actors with big bounding box given constants may not work properly like it's not possible to get close enough to actor from a given angle to make it move. --- apps/openmw/mwmechanics/aiescort.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 5dc1e44db..8c5230221 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -82,14 +82,16 @@ namespace MWMechanics mRemainingDuration = mDuration; return true; } - mMaxDist = 450; + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + mMaxDist = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())) + 450.0f; } else { // Stop moving if the player is too far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - mMaxDist = 250; + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + mMaxDist = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())) + 250.0f; } return false; From 453e94ea9fa31a0949b89b52edba4516586fc2ba Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 23 Mar 2021 23:59:45 +0100 Subject: [PATCH 25/96] Use half extents for destination distance tolerance in AiEscort For actors moving in water destination may be located at such z coordinate that they can't reach. --- apps/openmw/mwmechanics/aiescort.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 8c5230221..75c046110 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -73,25 +73,25 @@ namespace MWMechanics const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false); const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3(); const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3(); + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist) { const osg::Vec3f dest(mX, mY, mZ); - if (pathTo(actor, dest, duration)) //Returns true on path complete + if (pathTo(actor, dest, duration, maxHalfExtent)) //Returns true on path complete { mRemainingDuration = mDuration; return true; } - const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - mMaxDist = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())) + 450.0f; + mMaxDist = maxHalfExtent + 450.0f; } else { // Stop moving if the player is too far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - mMaxDist = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())) + 250.0f; + mMaxDist = maxHalfExtent + 250.0f; } return false; From e8c2cd15f09bc23bfa6d639bfd59e90ca5302d18 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 24 Mar 2021 00:03:22 +0100 Subject: [PATCH 26/96] Add #5914 to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c36ce3eb..f8d3a65f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ Bug #5902: NiZBufferProperty is unable to disable the depth test Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Bug #5912: ImprovedBound mod doesn't work + Bug #5914: BM: The Swimmer can't reach destination Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu From cf52bee188b1d16e524a17b80dd870a8b94d6d56 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 24 Mar 2021 16:32:15 +0400 Subject: [PATCH 27/96] Fix build with OSG 3.4 --- components/shader/shadervisitor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 1af9f94c5..612c9011d 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include From 91bca0cb1fbcf4ed08acb9dfdfedde63a7bdcf06 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 24 Mar 2021 13:28:34 -0700 Subject: [PATCH 28/96] attempt to fix build issue --- CI/before_script.msvc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index b2b6483ec..93d995c99 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -849,8 +849,8 @@ fi fi # check version - pip list | grep 'aqtinstall\s*1.1.3' - if [ $? -ne 0 ]; then + aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [[ $? -ne 0 ]] + if [[ $? -eq 0 ]]; then echo " Installing aqt wheel into virtualenv..." run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 fi From 72a2e3722ef0d9c273679df8ee4e5d85afa0245b Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 24 Mar 2021 14:01:40 -0700 Subject: [PATCH 29/96] update syntax --- CI/before_script.msvc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 93d995c99..e1afdb059 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -849,8 +849,8 @@ fi fi # check version - aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [[ $? -ne 0 ]] - if [[ $? -eq 0 ]]; then + aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [ $? -ne 0 ] + if [ $? -eq 0 ]; then echo " Installing aqt wheel into virtualenv..." run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 fi From e56efdd562eb9eefcce5ab536373f219ceb55b64 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 23 Mar 2021 15:52:37 -0700 Subject: [PATCH 30/96] change aim calculation --- apps/openmw/mwworld/worldimp.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 01b90e907..e5fa7322c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3867,11 +3867,14 @@ namespace MWWorld return false; } - osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) + osg::Vec3f World::aimToTarget(const ConstPtr &actor, const ConstPtr &target) { osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); - weaponPos.z() += mPhysics->getHalfExtents(actor).z(); - osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); + osg::Vec3f weaponHalfExtents = mPhysics->getHalfExtents(actor); + osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); + osg::Vec3f targetHalfExtents = mPhysics->getHalfExtents(target); + weaponPos.z() += weaponHalfExtents.z() * 2 * 0.75; // projectilemanager.cpp spawns bolts at 0.75 actor height + targetPos.z() += targetHalfExtents.z(); return (targetPos - weaponPos); } From 2cd96e56d5c6f5359f4f2e45d0b5e37572c7fc3e Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 24 Mar 2021 10:21:37 -0700 Subject: [PATCH 31/96] create constant and use constant in other parts of the code base --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/openmw/mwmechanics/aicast.cpp | 6 ++++-- apps/openmw/mwworld/projectilemanager.cpp | 3 +-- apps/openmw/mwworld/worldimp.cpp | 2 +- components/misc/constants.hpp | 3 +++ 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 53efdd286..b30168fd8 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -131,6 +131,7 @@ Programmers Martin Otto (MAtahualpa) Mateusz Kołaczek (PL_kolek) Mateusz Malisz (malice) + Max Henzerling (SaintMercury) megaton Michael Hogan (Xethik) Michael Mc Donnell diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c36ce3eb..fe5364312 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx + Bug #5680: Enemy AI incorrectly aims projectiles Bug #5681: Player character can clip or pass through bridges instead of colliding against them Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game Bug #5688: Water shader broken indoors with enable indoor shadows = false diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index af3aac340..630c04a6a 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -1,5 +1,7 @@ #include "aicast.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" @@ -54,12 +56,12 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac if (target != actor && target.getClass().isActor()) { osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target); - targetPos.z() += halfExtents.z() * 2 * 0.75f; + targetPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight; } osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - actorPos.z() += halfExtents.z() * 2 * 0.75f; + actorPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight; osg::Vec3f dir = targetPos - actorPos; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index f483905dd..b463fc09e 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -265,9 +265,8 @@ namespace MWWorld osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); if (caster.getClass().isActor()) { - // Spawn at 0.75 * ActorHeight // Note: we ignore the collision box offset, this is required to make some flying creatures work as intended. - pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * 0.75; + pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * Constants::TorsoHeight; } if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e5fa7322c..b37d326f0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3873,7 +3873,7 @@ namespace MWWorld osg::Vec3f weaponHalfExtents = mPhysics->getHalfExtents(actor); osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); osg::Vec3f targetHalfExtents = mPhysics->getHalfExtents(target); - weaponPos.z() += weaponHalfExtents.z() * 2 * 0.75; // projectilemanager.cpp spawns bolts at 0.75 actor height + weaponPos.z() += weaponHalfExtents.z() * 2 * Constants::TorsoHeight; targetPos.z() += targetHalfExtents.z(); return (targetPos - weaponPos); } diff --git a/components/misc/constants.hpp b/components/misc/constants.hpp index 1053b1c56..bfd3933fc 100644 --- a/components/misc/constants.hpp +++ b/components/misc/constants.hpp @@ -33,6 +33,9 @@ const std::string NightDayLabel = "NightDaySwitch"; // A label to mark visual switches for herbalism feature const std::string HerbalismLabel = "HerbalismSwitch"; +// Percentage height at which projectiles are spawned from an actor +const float TorsoHeight = 0.75f; + } #endif From dc09616e59a2b81f6fa9d44708f47cbf09757def Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 24 Mar 2021 11:15:09 -0700 Subject: [PATCH 32/96] change bugfix name to be same as ticket name --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe5364312..d0217d835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,7 +88,7 @@ Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx - Bug #5680: Enemy AI incorrectly aims projectiles + Bug #5680: Bull Netches incorrectly aim over the player character's head and always miss Bug #5681: Player character can clip or pass through bridges instead of colliding against them Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game Bug #5688: Water shader broken indoors with enable indoor shadows = false From ccd3cbc69abf40ab4ddfac4ae9a7e6fa79666fe0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 26 Mar 2021 23:36:12 +0100 Subject: [PATCH 33/96] Use saved actor position instead of reading again RefData in unstuck. It is a race condition to do so. --- apps/openmw/mwphysics/movementsolver.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index a393ec578..1c04ee65a 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -459,8 +459,7 @@ namespace MWPhysics const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); // use a 3d approximation of the movement vector to better judge player intent - const ESM::Position& refpos = ptr.getRefData().getPosition(); - auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; + auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; // try to pop outside of the world before doing anything else if we're inside of it if (!physicActor->getOnGround() || physicActor->getOnSlope()) velocity += physicActor->getInertialForce(); From dbd6e3bfee7b58d5d527a87c4b31d3ecf0a52658 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 26 Mar 2021 23:43:49 +0100 Subject: [PATCH 34/96] Replace pointless usage of shared_ptr by unique_ptr / non-owning raw pointer for btCollisionWorld. --- apps/openmw/mwphysics/mtphysics.cpp | 14 +++++++------- apps/openmw/mwphysics/mtphysics.hpp | 4 ++-- apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 11eb7f909..209c5aa65 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -137,11 +137,11 @@ namespace namespace MWPhysics { - PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr collisionWorld) + PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld) : mDefaultPhysicsDt(physicsDt) , mPhysicsDt(physicsDt) , mTimeAccum(0.f) - , mCollisionWorld(std::move(collisionWorld)) + , mCollisionWorld(collisionWorld) , mNumJobs(0) , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) @@ -185,7 +185,7 @@ namespace MWPhysics if (data.mActor.lock()) { std::unique_lock lock(mCollisionWorldMutex); - MovementSolver::unstuck(data, mCollisionWorld.get()); + MovementSolver::unstuck(data, mCollisionWorld); } }); @@ -381,7 +381,7 @@ namespace MWPhysics void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) { std::shared_lock lock(mCollisionWorldMutex); - ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback); + ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback); } std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) @@ -532,7 +532,7 @@ namespace MWPhysics if(const auto actor = mActorsFrameData[job].mActor.lock()) { MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); - MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); + MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); } } @@ -594,8 +594,8 @@ namespace MWPhysics { for (auto& actorData : mActorsFrameData) { - MovementSolver::unstuck(actorData, mCollisionWorld.get()); - MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); + MovementSolver::unstuck(actorData, mCollisionWorld); + MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData); } updateActorsPositions(); diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 137755c21..22cab3241 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -25,7 +25,7 @@ namespace MWPhysics class PhysicsTaskScheduler { public: - PhysicsTaskScheduler(float physicsDt, std::shared_ptr collisionWorld); + PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld); ~PhysicsTaskScheduler(); /// @brief move actors taking into account desired movements and collisions @@ -67,7 +67,7 @@ namespace MWPhysics float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; - std::shared_ptr mCollisionWorld; + btCollisionWorld* mCollisionWorld; std::vector mLOSCache; std::set, std::owner_less>> mUpdateAabb; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 65ed13f80..f93ffe76d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -79,7 +79,7 @@ namespace MWPhysics mDispatcher = std::make_unique(mCollisionConfiguration.get()); mBroadphase = std::make_unique(); - mCollisionWorld = std::make_shared(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get()); + mCollisionWorld = std::make_unique(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get()); // Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this. // Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb. @@ -97,7 +97,7 @@ namespace MWPhysics } } - mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld); + mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld.get()); mDebugDrawer = std::make_unique(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 354823986..5ccbf1a38 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -259,7 +259,7 @@ namespace MWPhysics std::unique_ptr mBroadphase; std::unique_ptr mCollisionConfiguration; std::unique_ptr mDispatcher; - std::shared_ptr mCollisionWorld; + std::unique_ptr mCollisionWorld; std::unique_ptr mTaskScheduler; std::unique_ptr mShapeManager; From b58244ac26f3fa970863c2fc713de3368cba3e7f Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 26 Mar 2021 23:45:56 +0100 Subject: [PATCH 35/96] Guard the Bullet drawing method with a read lock on the btCollisionWorld. It closes a race on the collision shapes coordinates. --- apps/openmw/mwphysics/mtphysics.cpp | 10 +++++++++- apps/openmw/mwphysics/mtphysics.hpp | 9 ++++++++- apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 209c5aa65..4be8b2396 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -9,6 +9,7 @@ #include "components/settings/settings.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" @@ -137,11 +138,12 @@ namespace namespace MWPhysics { - PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld) + PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer) : mDefaultPhysicsDt(physicsDt) , mPhysicsDt(physicsDt) , mTimeAccum(0.f) , mCollisionWorld(collisionWorld) + , mDebugDrawer(debugDrawer) , mNumJobs(0) , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) @@ -626,4 +628,10 @@ namespace MWPhysics mTimeBegin = mTimer->tick(); mFrameNumber = frameNumber; } + + void PhysicsTaskScheduler::debugDraw() + { + std::shared_lock lock(mCollisionWorldMutex); + mDebugDrawer->step(); + } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 22cab3241..6d2c392c0 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -20,12 +20,17 @@ namespace Misc class Barrier; } +namespace MWRender +{ + class DebugDrawer; +} + namespace MWPhysics { class PhysicsTaskScheduler { public: - PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld); + PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer); ~PhysicsTaskScheduler(); /// @brief move actors taking into account desired movements and collisions @@ -49,6 +54,7 @@ namespace MWPhysics void removeCollisionObject(btCollisionObject* collisionObject); void updateSingleAabb(std::weak_ptr ptr, bool immediate=false); bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2); + void debugDraw(); private: void syncComputation(); @@ -68,6 +74,7 @@ namespace MWPhysics float mPhysicsDt; float mTimeAccum; btCollisionWorld* mCollisionWorld; + MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; std::set, std::owner_less>> mUpdateAabb; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f93ffe76d..ac8dd92f8 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -97,8 +97,8 @@ namespace MWPhysics } } - mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld.get()); mDebugDrawer = std::make_unique(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled); + mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld.get(), mDebugDrawer.get()); } PhysicsSystem::~PhysicsSystem() @@ -827,7 +827,7 @@ namespace MWPhysics void PhysicsSystem::debugDraw() { if (mDebugDrawEnabled) - mDebugDrawer->step(); + mTaskScheduler->debugDraw(); } bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const From 7a67492d81858e53224703d4267dd612e43ae2c8 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 28 Mar 2021 21:19:14 +0200 Subject: [PATCH 36/96] Unbreak SetPos and the mods using it. To make SetPos works with async physics, it was modified to register a position offset that would be applied to the real position during the simulation. A common pattern to teleport NPC in scripts is a sequence of SetPos/Disable/Enable in the same frame. Since Disable/Enable creates a new physics actor using last known RefData::Position, the registered offset never get a chance to be applied. Modify disable() to call moveObject with the offset applied, so that the newly created physics actor will have up-to-date position --- apps/openmw/mwphysics/actor.cpp | 8 +++++--- apps/openmw/mwworld/worldimp.cpp | 13 +++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 905034cde..bf7503e72 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -123,6 +123,7 @@ void Actor::updatePosition() mSimulationPosition = mWorldPosition; mStandingOnPtr = nullptr; mSkipSimulation = true; + mPositionOffset = osg::Vec3f(); } void Actor::updateWorldPosition() @@ -179,9 +180,9 @@ bool Actor::setPosition(const osg::Vec3f& position) if (mSkipSimulation) return false; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; - mPreviousPosition = mPosition + mPositionOffset; - mPosition = position + mPositionOffset; - mPositionOffset = osg::Vec3f(); + applyOffsetChange(); + mPreviousPosition = mPosition; + mPosition = position; return hasChanged; } @@ -198,6 +199,7 @@ void Actor::applyOffsetChange() mWorldPosition += mPositionOffset; mPosition += mPositionOffset; mPreviousPosition += mPositionOffset; + mSimulationPosition += mPositionOffset; mPositionOffset = osg::Vec3f(); mWorldPositionChanged = true; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d6a4de71b..5e0cdbb2b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -862,6 +862,19 @@ namespace MWWorld if (reference == getPlayerPtr()) throw std::runtime_error("can not disable player object"); + // A common pattern to teleport NPC in scripts is a sequence of SetPos/Disable/Enable + // Disable/Enable create a new physics actor, and so the SetPos call is lost + // Call moveObject so that the newly created physics actor will have up-to-date position + if (reference.getClass().isActor()) + { + auto* physactor = mPhysics->getActor(reference); + if (physactor) + { + physactor->applyOffsetChange(); + const auto position = physactor->getSimulationPosition(); + moveObject(reference, position.x(), position.y(), position.z(), true); + } + } reference.getRefData().disable(); if (reference.getCellRef().getRefNum().hasContentFile()) From 50352daf90ab5ba44bf6aab3368403604ddb4329 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 29 Mar 2021 14:45:07 +0400 Subject: [PATCH 37/96] Rework knockdown and knockout animations fallbacks --- apps/openmw/mwmechanics/character.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 99f8632f3..8c58fd943 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -211,38 +211,48 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) if(mHitState == CharState_None) { if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 - || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) - && mAnimation->hasAnimation("knockout")) + || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0)) { mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds if (isSwimming && mAnimation->hasAnimation("swimknockout")) { mHitState = CharState_SwimKnockOut; mCurrentHit = "swimknockout"; + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); } - else + else if (!isSwimming && mAnimation->hasAnimation("knockout")) { mHitState = CharState_KnockOut; mCurrentHit = "knockout"; + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); + } + else + { + // Knockout animations are missing. Fall back to idle animation, so target actor still can be killed via HtH. + mCurrentHit.erase(); } - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); } - else if(knockdown && mAnimation->hasAnimation("knockdown")) + else if (knockdown) { if (isSwimming && mAnimation->hasAnimation("swimknockdown")) { mHitState = CharState_SwimKnockDown; mCurrentHit = "swimknockdown"; + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); } - else + else if (!isSwimming && mAnimation->hasAnimation("knockdown")) { mHitState = CharState_KnockDown; mCurrentHit = "knockdown"; + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } + else + { + // Knockdown animation is missing. Cancel knockdown state. + mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); } - - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); } else if (recovery) { From 0fac172413df82f8e2613d3b9bb9e4b8450dc9da Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 29 Mar 2021 21:46:08 +0200 Subject: [PATCH 38/96] Remove wrong line that slipped in yesterday. --- apps/openmw/mwphysics/actor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index bf7503e72..a2454345b 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -123,7 +123,6 @@ void Actor::updatePosition() mSimulationPosition = mWorldPosition; mStandingOnPtr = nullptr; mSkipSimulation = true; - mPositionOffset = osg::Vec3f(); } void Actor::updateWorldPosition() From c6033b4de6653ac597e3604ac8d73b3fbee27d4c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 29 Mar 2021 21:00:28 +0000 Subject: [PATCH 39/96] Fix #5904 --- components/sceneutil/util.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index fa3c7d26d..cc8a2f44b 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -263,6 +264,10 @@ osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resourc bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture * texture, unsigned int level, unsigned int face, bool mipMapGeneration) { +#if OSG_VERSION_LESS_THAN(3, 6, 6) + // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 + osg::GLExtensions::Get(0, false)->glRenderbufferStorageMultisampleCoverageNV = nullptr; +#endif unsigned int samples = 0; unsigned int colourSamples = 0; bool addMSAAIntermediateTarget = Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1; From 44829046a0fe55507632ac5e9e26ec72a7d38b77 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Tue, 30 Mar 2021 08:00:00 +0000 Subject: [PATCH 40/96] Update Gentoo maintainers (me) --- AUTHORS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index b30168fd8..fa3a4fba2 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -4,7 +4,7 @@ Contributors The OpenMW project was started in 2008 by Nicolay Korslund. In the course of years many people have contributed to the project. -If you feel your name is missing from this list, please notify a developer. +If you feel your name is missing from this list, please add it to `AUTHORS.md`. Programmers @@ -236,6 +236,7 @@ Packagers --------- Alexander Olofsson (Ace) - Windows + Alexey Sokolov (DarthGandalf) - Gentoo Linux Bret Curtis (psi29a) - Debian and Ubuntu Linux Edmondo Tommasina (edmondo) - Gentoo Linux Julian Ospald (hasufell) - Gentoo Linux From ad0fe2ee2951aa3fbf605639ed358500e0f82d25 Mon Sep 17 00:00:00 2001 From: Alexander Olofsson Date: Tue, 30 Mar 2021 11:31:36 +0200 Subject: [PATCH 41/96] Update Flatpak maintainer --- AUTHORS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index fa3a4fba2..d2de85747 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -24,7 +24,7 @@ Programmers Alex McKibben alexanderkjall Alexander Nadeau (wareya) - Alexander Olofsson (Ace) + Alexander Olofsson (Ananace) Alex Rice Alex S (docwest) Allofich @@ -235,7 +235,7 @@ Documentation Packagers --------- - Alexander Olofsson (Ace) - Windows + Alexander Olofsson (Ananace) - Windows and Flatpak Alexey Sokolov (DarthGandalf) - Gentoo Linux Bret Curtis (psi29a) - Debian and Ubuntu Linux Edmondo Tommasina (edmondo) - Gentoo Linux From 1ab5a9f5308fe2e6fe389bfb218586aa57b311d1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 30 Mar 2021 15:32:01 +0400 Subject: [PATCH 42/96] Revert "Merge branch 'refractiontest' into 'master'" This reverts commit b31459cc009105d211cb8b186e6223894727eece, reversing changes made to 369adf158381c1e25d64491c04e50b9020d80782. --- files/shaders/water_fragment.glsl | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index db208ea42..d9b9463ad 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -29,21 +29,20 @@ const float BUMP_RAIN = 2.5; const float REFL_BUMP = 0.10; // reflection distortion amount const float REFR_BUMP = 0.07; // refraction distortion amount +const float SCATTER_AMOUNT = 0.3; // amount of sunlight scattering +const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering + const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SPEC_HARDNESS = 256.0; // specular highlights hardness const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be suppressed for reflections and refractions (prevents artifacts at shores) -const float BUMP_SUPPRESS_DEPTH_SS = 1000.0; // modifier using screenspace depth (helps prevent same artifacts but at higher distances) const vec2 WIND_DIR = vec2(0.5f, -0.8f); const float WIND_SPEED = 0.2f; const vec3 WATER_COLOR = vec3(0.090195, 0.115685, 0.12745); -const float SCATTER_AMOUNT = 0.5; // amount of sunlight scattering -const vec3 SCATTER_COLOUR = WATER_COLOR * 8.0; // colour of sunlight scattering - // ---------------- rain ripples related stuff --------------------- const float RAIN_RIPPLE_GAPS = 5.0; @@ -219,10 +218,7 @@ void main(void) float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-screenCoordsOffset).x) * radialise; float surfaceDepth = linearizeDepth(gl_FragCoord.z) * radialise; float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum - screenCoordsOffset *= clamp( - realWaterDepth / (BUMP_SUPPRESS_DEPTH - * max(1, depthSample / BUMP_SUPPRESS_DEPTH_SS)) // suppress more at distance - ,0 ,1); + screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); #endif // reflection vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; @@ -240,11 +236,7 @@ void main(void) if (cameraPos.z < 0.0) refraction = clamp(refraction * 1.5, 0.0, 1.0); else - { - vec3 refractionA = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0)); - vec3 refractionB = mix(refraction, waterColor, clamp(realWaterDepth/VISIBILITY, 0.0, 1.0)); - refraction = mix(refractionA, refractionB, 0.8); - } + refraction = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0)); // sunlight scattering // normal for sunlight scattering From 44bd81e8f003f51ef5b6dfc6260245f8fedbc587 Mon Sep 17 00:00:00 2001 From: Alexander Olofsson Date: Wed, 31 Mar 2021 18:15:22 +0200 Subject: [PATCH 43/96] Use the SPDX license identifier in appdata XML --- files/openmw.appdata.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml index f4bdb5c91..a9906aae6 100644 --- a/files/openmw.appdata.xml +++ b/files/openmw.appdata.xml @@ -5,8 +5,8 @@ Copyright 2020 Bret Curtis --> org.openmw.launcher.desktop - GPL-3+ - GPL-3+ + GPL-3.0-or-later + GPL-3.0-or-later OpenMW Unofficial open source engine re-implementation of the game Morrowind From 9cae7882dd53480bc75fa00de0954d00eeef20ce Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 31 Mar 2021 19:34:06 +0200 Subject: [PATCH 44/96] Fix a bug that was triggered with multi mark mod. When a script calls SetPos for x,y,z in sequence on an actor, we need to make sure that the actor will not spawn under ground at x,y coordinates. Now that change of coordinates are cumulated and applied all at once, we need to account for the whole offset. To this end move the terrain height check inside of Actor class. --- apps/openmw/mwphysics/actor.cpp | 11 +++++++++++ apps/openmw/mwscript/transformationextensions.cpp | 11 ----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index a2454345b..776212ede 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -195,6 +197,15 @@ void Actor::applyOffsetChange() { if (mPositionOffset.length() == 0) return; + if (mPositionOffset.z() != 0) + { + // Often, offset are set in sequence x, y, z + // We don't want actors to be moved under the ground + // Check terrain height at new coordinate and update z offset if necessary + const auto pos = mWorldPosition + mPositionOffset; + const auto terrainHeight = mPtr.getCell()->isExterior() ? MWBase::Environment::get().getWorld()->getTerrainHeightAt(pos) : -std::numeric_limits::max(); + mPositionOffset.z() = std::max(pos.z(), terrainHeight) - mWorldPosition.z(); + } mWorldPosition += mPositionOffset; mPosition += mPositionOffset; mPreviousPosition += mPositionOffset; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index e8b406977..6b92378c7 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -284,17 +284,6 @@ namespace MWScript } else if(axis == "z") { - // We should not place actors under ground - if (ptr.getClass().isActor()) - { - float terrainHeight = -std::numeric_limits::max(); - if (ptr.getCell()->isExterior()) - terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos); - - if (pos < terrainHeight) - pos = terrainHeight; - } - newPos[2] = pos; } else From b8089680705c0008d5a4a08dcf5c185dea4950b2 Mon Sep 17 00:00:00 2001 From: "Hristos N. Triantafillou" Date: Wed, 24 Mar 2021 18:19:05 -0500 Subject: [PATCH 45/96] Use the new macOS image for the CI runner open beta Also add a second build for wider version coverage. --- .gitlab-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 36c8a6ab7..713cc2601 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -94,6 +94,7 @@ Debian_Clang_tests: BUILD_TESTS_ONLY: 1 MacOS: + image: macos-11-xcode-12 tags: - macos stage: build @@ -111,6 +112,10 @@ MacOS: - build/OpenMW-*.dmg - "build/**/*.log" +macOS10.15_Xcode11: + extends: MacOS + image: macos-10.15-xcode-11 + variables: &engine-targets targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" package: "Engine" From 6c1844930432831e52c1d4aab7e362c81d213cb8 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Apr 2021 02:15:00 +0200 Subject: [PATCH 46/96] Remove GTest and ExternalProject from FindGMock.cmake --- cmake/FindGMock.cmake | 320 +----------------------------------------- 1 file changed, 1 insertion(+), 319 deletions(-) diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake index 8d7324242..502910a9d 100644 --- a/cmake/FindGMock.cmake +++ b/cmake/FindGMock.cmake @@ -1,15 +1,11 @@ # Get the Google C++ Mocking Framework. -# (This file is almost an copy of the original FindGTest.cmake file, -# altered to download and compile GMock and GTest if not found -# in GMOCK_ROOT or GTEST_ROOT respectively, +# (This file is almost an copy of the original FindGTest.cmake file for GMock, # feel free to use it as it is or modify it for your own needs.) # # Defines the following variables: # # GMOCK_FOUND - Found or got the Google Mocking framework -# GTEST_FOUND - Found or got the Google Testing framework # GMOCK_INCLUDE_DIRS - GMock include directory -# GTEST_INCLUDE_DIRS - GTest include direcotry # # Also defines the library variables below as normal variables # @@ -17,14 +13,8 @@ # GMOCK_LIBRARIES - libgmock # GMOCK_MAIN_LIBRARIES - libgmock-main # -# GTEST_BOTH_LIBRARIES - Both libgtest & libgtest_main -# GTEST_LIBRARIES - libgtest -# GTEST_MAIN_LIBRARIES - libgtest_main -# # Accepts the following variables as input: # -# GMOCK_ROOT - The root directory of the gmock install prefix -# GTEST_ROOT - The root directory of the gtest install prefix # GMOCK_SRC_DIR -The directory of the gmock sources # GMOCK_VER - The version of the gmock sources to be downloaded # @@ -101,48 +91,6 @@ # # * Kitware, Inc. #============================================================================= -# Thanks to Daniel Blezek for the GTEST_ADD_TESTS code - -function(gtest_add_tests executable extra_args) - if(NOT ARGN) - message(FATAL_ERROR "Missing ARGN: Read the documentation for GTEST_ADD_TESTS") - endif() - if(ARGN STREQUAL "AUTO") - # obtain sources used for building that executable - get_property(ARGN TARGET ${executable} PROPERTY SOURCES) - endif() - set(gtest_case_name_regex ".*\\( *([A-Za-z_0-9]+) *, *([A-Za-z_0-9]+) *\\).*") - set(gtest_test_type_regex "(TYPED_TEST|TEST_?[FP]?)") - foreach(source ${ARGN}) - file(READ "${source}" contents) - string(REGEX MATCHALL "${gtest_test_type_regex} *\\(([A-Za-z_0-9 ,]+)\\)" found_tests ${contents}) - foreach(hit ${found_tests}) - string(REGEX MATCH "${gtest_test_type_regex}" test_type ${hit}) - - # Parameterized tests have a different signature for the filter - if("x${test_type}" STREQUAL "xTEST_P") - string(REGEX REPLACE ${gtest_case_name_regex} "*/\\1.\\2/*" test_name ${hit}) - elseif("x${test_type}" STREQUAL "xTEST_F" OR "x${test_type}" STREQUAL "xTEST") - string(REGEX REPLACE ${gtest_case_name_regex} "\\1.\\2" test_name ${hit}) - elseif("x${test_type}" STREQUAL "xTYPED_TEST") - string(REGEX REPLACE ${gtest_case_name_regex} "\\1/*.\\2" test_name ${hit}) - else() - message(WARNING "Could not parse GTest ${hit} for adding to CTest.") - continue() - endif() - add_test(NAME ${test_name} COMMAND ${executable} --gtest_filter=${test_name} ${extra_args}) - endforeach() - endforeach() -endfunction() - -function(_append_debugs _endvar _library) - if(${_library} AND ${_library}_DEBUG) - set(_output optimized ${${_library}} debug ${${_library}_DEBUG}) - else() - set(_output ${${_library}}) - endif() - set(${_endvar} ${_output} PARENT_SCOPE) -endfunction() function(_gmock_find_library _name) find_library(${_name} @@ -155,38 +103,20 @@ function(_gmock_find_library _name) mark_as_advanced(${_name}) endfunction() -function(_gtest_find_library _name) - find_library(${_name} - NAMES ${ARGN} - HINTS - ENV GTEST_ROOT - ${GTEST_ROOT} - PATH_SUFFIXES ${_gtest_libpath_suffixes} - ) - mark_as_advanced(${_name}) -endfunction() - if(NOT DEFINED GMOCK_MSVC_SEARCH) set(GMOCK_MSVC_SEARCH MD) endif() set(_gmock_libpath_suffixes lib) -set(_gtest_libpath_suffixes lib) if(MSVC) if(GMOCK_MSVC_SEARCH STREQUAL "MD") list(APPEND _gmock_libpath_suffixes msvc/gmock-md/Debug msvc/gmock-md/Release) - list(APPEND _gtest_libpath_suffixes - msvc/gtest-md/Debug - msvc/gtest-md/Release) elseif(GMOCK_MSVC_SEARCH STREQUAL "MT") list(APPEND _gmock_libpath_suffixes msvc/gmock/Debug msvc/gmock/Release) - list(APPEND _gtest_libpath_suffixes - msvc/gtest/Debug - msvc/gtest/Release) endif() endif() @@ -197,13 +127,6 @@ find_path(GMOCK_INCLUDE_DIR gmock/gmock.h ) mark_as_advanced(GMOCK_INCLUDE_DIR) -find_path(GTEST_INCLUDE_DIR gtest/gtest.h - HINTS - $ENV{GTEST_ROOT}/include - ${GTEST_ROOT}/include - ) -mark_as_advanced(GTEST_INCLUDE_DIR) - if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD") # The provided /MD project files for Google Mock add -md suffixes to the # library names. @@ -211,28 +134,12 @@ if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD") _gmock_find_library(GMOCK_LIBRARY_DEBUG gmock-mdd gmockd) _gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main-md gmock_main) _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_main-mdd gmock_maind) - - _gtest_find_library(GTEST_LIBRARY gtest-md gtest) - _gtest_find_library(GTEST_LIBRARY_DEBUG gtest-mdd gtestd) - _gtest_find_library(GTEST_MAIN_LIBRARY gtest_main-md gtest_main) - _gtest_find_library(GTEST_MAIN_LIBRARY_DEBUG gtest_main-mdd gtest_maind) else() _gmock_find_library(GMOCK_LIBRARY gmock) _gmock_find_library(GMOCK_LIBRARY_DEBUG gmockd) _gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main) _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_maind) - _gtest_find_library(GTEST_LIBRARY gtest) - _gtest_find_library(GTEST_LIBRARY_DEBUG gtestd) - _gtest_find_library(GTEST_MAIN_LIBRARY gtest_main) - _gtest_find_library(GTEST_MAIN_LIBRARY_DEBUG gtest_maind) -endif() - -if(NOT TARGET GTest::GTest) - add_library(GTest::GTest UNKNOWN IMPORTED) -endif() -if(NOT TARGET GTest::Main) - add_library(GTest::Main UNKNOWN IMPORTED) endif() if(NOT TARGET GMock::GMock) @@ -244,224 +151,17 @@ if(NOT TARGET GMock::Main) endif() set(GMOCK_LIBRARY_EXISTS OFF) -set(GTEST_LIBRARY_EXISTS OFF) if(EXISTS "${GMOCK_LIBRARY}" OR EXISTS "${GMOCK_LIBRARY_DEBUG}" AND GMOCK_INCLUDE_DIR) set(GMOCK_LIBRARY_EXISTS ON) endif() -if(EXISTS "${GTEST_LIBRARY}" OR EXISTS "${GTEST_LIBRARY_DEBUG}" AND GTEST_INCLUDE_DIR) - set(GTEST_LIBRARY_EXISTS ON) -endif() - -if(NOT (${GMOCK_LIBRARY_EXISTS} AND ${GTEST_LIBRARY_EXISTS})) - - include(ExternalProject) - - if(GTEST_USE_STATIC_LIBS) - set(GTEST_CMAKE_ARGS -Dgtest_force_shared_crt:BOOL=ON -DBUILD_SHARED_LIBS=OFF) - if(BUILD_SHARED_LIBS) - list(APPEND GTEST_CMAKE_ARGS - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -Dgtest_hide_internal_symbols=ON - -DCMAKE_CXX_VISIBILITY_PRESET=hidden - -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON - -DCMAKE_POLICY_DEFAULT_CMP0063=NEW - ) - endif() - set(GTEST_LIBRARY_PREFIX ${CMAKE_STATIC_LIBRARY_PREFIX}) - else() - set(GTEST_CMAKE_ARGS -DBUILD_SHARED_LIBS=ON) - set(GTEST_LIBRARY_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX}) - endif() - if(WIN32) - list(APPEND GTEST_CMAKE_ARGS -Dgtest_disable_pthreads=ON) - endif() - - if("${GMOCK_SRC_DIR}" STREQUAL "") - message(STATUS "Downloading GMock / GTest version ${GMOCK_VER} from git") - if("${GMOCK_VER}" STREQUAL "1.6.0" OR "${GMOCK_VER}" STREQUAL "1.7.0") - set(GTEST_BIN_DIR "${GMOCK_ROOT}/src/gtest-build") - set(GTEST_LIBRARY "${GTEST_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(GTEST_MAIN_LIBRARY "${GTEST_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") - mark_as_advanced(GTEST_LIBRARY) - mark_as_advanced(GTEST_MAIN_LIBRARY) - - externalproject_add( - gtest - GIT_REPOSITORY "https://github.com/google/googletest.git" - GIT_TAG "release-${GMOCK_VER}" - PREFIX ${GMOCK_ROOT} - INSTALL_COMMAND "" - LOG_DOWNLOAD ON - LOG_CONFIGURE ON - LOG_BUILD ON - CMAKE_ARGS - ${GTEST_CMAKE_ARGS} - BINARY_DIR ${GTEST_BIN_DIR} - BUILD_BYPRODUCTS - "${GTEST_LIBRARY}" - "${GTEST_MAIN_LIBRARY}" - ) - - set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build") - set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}") - mark_as_advanced(GMOCK_LIBRARY) - mark_as_advanced(GMOCK_MAIN_LIBRARY) - - externalproject_add( - gmock - GIT_REPOSITORY "https://github.com/google/googlemock.git" - GIT_TAG "release-${GMOCK_VER}" - PREFIX ${GMOCK_ROOT} - INSTALL_COMMAND "" - LOG_DOWNLOAD ON - LOG_CONFIGURE ON - LOG_BUILD ON - CMAKE_ARGS - ${GTEST_CMAKE_ARGS} - BINARY_DIR ${GMOCK_BIN_DIR} - BUILD_BYPRODUCTS - "${GMOCK_LIBRARY}" - "${GMOCK_MAIN_LIBRARY}" - ) - - add_dependencies(gmock gtest) - - add_dependencies(GTest::GTest gtest) - add_dependencies(GTest::Main gtest) - add_dependencies(GMock::GMock gmock) - add_dependencies(GMock::Main gmock) - - externalproject_get_property(gtest source_dir) - set(GTEST_INCLUDE_DIR "${source_dir}/include") - mark_as_advanced(GTEST_INCLUDE_DIR) - externalproject_get_property(gmock source_dir) - set(GMOCK_INCLUDE_DIR "${source_dir}/include") - mark_as_advanced(GMOCK_INCLUDE_DIR) - else() #1.8.0 - set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build") - set(GTEST_LIBRARY "${GMOCK_BIN_DIR}/googlemock/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(GTEST_MAIN_LIBRARY "${GMOCK_BIN_DIR}/googlemock/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/googlemock/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/googlemock/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}") - mark_as_advanced(GTEST_LIBRARY) - mark_as_advanced(GTEST_MAIN_LIBRARY) - mark_as_advanced(GMOCK_LIBRARY) - mark_as_advanced(GMOCK_MAIN_LIBRARY) - - externalproject_add( - gmock - GIT_REPOSITORY "https://github.com/google/googletest.git" - GIT_TAG "release-${GMOCK_VER}" - PREFIX ${GMOCK_ROOT} - INSTALL_COMMAND "" - LOG_DOWNLOAD ON - LOG_CONFIGURE ON - LOG_BUILD ON - CMAKE_ARGS - ${GTEST_CMAKE_ARGS} - BINARY_DIR "${GMOCK_BIN_DIR}" - BUILD_BYPRODUCTS - "${GTEST_LIBRARY}" - "${GTEST_MAIN_LIBRARY}" - "${GMOCK_LIBRARY}" - "${GMOCK_MAIN_LIBRARY}" - ) - - add_dependencies(GTest::GTest gmock) - add_dependencies(GTest::Main gmock) - add_dependencies(GMock::GMock gmock) - add_dependencies(GMock::Main gmock) - - externalproject_get_property(gmock source_dir) - set(GTEST_INCLUDE_DIR "${source_dir}/googletest/include") - set(GMOCK_INCLUDE_DIR "${source_dir}/googlemock/include") - mark_as_advanced(GMOCK_INCLUDE_DIR) - mark_as_advanced(GTEST_INCLUDE_DIR) - endif() - - # Prevent CMake from complaining about these directories missing when the libgtest/libgmock targets get used as dependencies - file(MAKE_DIRECTORY ${GTEST_INCLUDE_DIR} ${GMOCK_INCLUDE_DIR}) - else() - message(STATUS "Building Gmock / Gtest from dir ${GMOCK_SRC_DIR}") - - set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build") - set(GTEST_LIBRARY "${GMOCK_BIN_DIR}/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(GTEST_MAIN_LIBRARY "${GMOCK_BIN_DIR}/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}") - mark_as_advanced(GTEST_LIBRARY) - mark_as_advanced(GTEST_MAIN_LIBRARY) - mark_as_advanced(GMOCK_LIBRARY) - mark_as_advanced(GMOCK_MAIN_LIBRARY) - - if(EXISTS "${GMOCK_SRC_DIR}/gtest/include/gtest/gtest.h") - set(GTEST_INCLUDE_DIR "${GMOCK_SRC_DIR}/gtest/include") - mark_as_advanced(GTEST_INCLUDE_DIR) - endif() - if(EXISTS "${GMOCK_SRC_DIR}/include/gmock/gmock.h") - set(GMOCK_INCLUDE_DIR "${GMOCK_SRC_DIR}/include") - mark_as_advanced(GMOCK_INCLUDE_DIR) - elseif(EXISTS "${GMOCK_SRC_DIR}/../../include/gmock/gmock.h") - set(GMOCK_INCLUDE_DIR "${GMOCK_SRC_DIR}/../../include") - if(IS_ABSOLUTE "${GMOCK_INCLUDE_DIR}") - get_filename_component(GMOCK_INCLUDE_DIR "${GMOCK_INCLUDE_DIR}" ABSOLUTE) - endif() - mark_as_advanced(GMOCK_INCLUDE_DIR) - endif() - - externalproject_add( - gmock - SOURCE_DIR ${GMOCK_SRC_DIR} - PREFIX ${GMOCK_ROOT} - INSTALL_COMMAND "" - LOG_DOWNLOAD ON - LOG_CONFIGURE ON - LOG_BUILD ON - CMAKE_ARGS - ${GTEST_CMAKE_ARGS} - BINARY_DIR "${GMOCK_BIN_DIR}" - BUILD_BYPRODUCTS - "${GTEST_LIBRARY}" - "${GTEST_MAIN_LIBRARY}" - "${GMOCK_LIBRARY}" - "${GMOCK_MAIN_LIBRARY}" - ) - - add_dependencies(GTest::GTest gmock) - add_dependencies(GTest::Main gmock) - add_dependencies(GMock::GMock gmock) - add_dependencies(GMock::Main gmock) - endif() -endif() - include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(GTest DEFAULT_MSG GTEST_LIBRARY GTEST_INCLUDE_DIR GTEST_MAIN_LIBRARY) find_package_handle_standard_args(GMock DEFAULT_MSG GMOCK_LIBRARY GMOCK_INCLUDE_DIR GMOCK_MAIN_LIBRARY) include(CMakeFindDependencyMacro) find_dependency(Threads) -set_target_properties(GTest::GTest PROPERTIES - INTERFACE_LINK_LIBRARIES "Threads::Threads" - IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" - IMPORTED_LOCATION "${GTEST_LIBRARY}" - ) - -if(GTEST_INCLUDE_DIR) - set_target_properties(GTest::GTest PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}" - INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}" - ) -endif() - -set_target_properties(GTest::Main PROPERTIES - INTERFACE_LINK_LIBRARIES "GTest::GTest" - IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" - IMPORTED_LOCATION "${GTEST_MAIN_LIBRARY}") - set_target_properties(GMock::GMock PROPERTIES INTERFACE_LINK_LIBRARIES "Threads::Threads" IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" @@ -477,13 +177,6 @@ if(GMOCK_INCLUDE_DIR) # so just specify it on the link interface. set_property(TARGET GMock::GMock APPEND PROPERTY INTERFACE_LINK_LIBRARIES GTest::GTest) - elseif(GTEST_INCLUDE_DIR) - # GMock 1.7 and beyond doesn't have it as a link-time dependency anymore, - # so merge it's compile-time interface (include dirs) with ours. - set_property(TARGET GMock::GMock APPEND PROPERTY - INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}") - set_property(TARGET GMock::GMock APPEND PROPERTY - INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}") endif() endif() @@ -492,17 +185,6 @@ set_target_properties(GMock::Main PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY}") -if(GTEST_FOUND) - set(GTEST_INCLUDE_DIRS ${GTEST_INCLUDE_DIR}) - set(GTEST_LIBRARIES GTest::GTest) - set(GTEST_MAIN_LIBRARIES GTest::Main) - set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES}) - if(VERBOSE) - message(STATUS "GTest includes: ${GTEST_INCLUDE_DIRS}") - message(STATUS "GTest libs: ${GTEST_BOTH_LIBRARIES}") - endif() -endif() - if(GMOCK_FOUND) set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR}) set(GMOCK_LIBRARIES GMock::GMock) From db32b8de917b2290c679d8ded4b0057ffdd8b61e Mon Sep 17 00:00:00 2001 From: Alexander Olofsson Date: Sat, 3 Apr 2021 13:41:41 +0200 Subject: [PATCH 47/96] Return to CC0-licensed appdata metadata --- files/openmw.appdata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml index a9906aae6..8f3865f09 100644 --- a/files/openmw.appdata.xml +++ b/files/openmw.appdata.xml @@ -5,7 +5,7 @@ Copyright 2020 Bret Curtis --> org.openmw.launcher.desktop - GPL-3.0-or-later + CC0-1.0 GPL-3.0-or-later OpenMW Unofficial open source engine re-implementation of the game Morrowind From 03fc3ec80351c4edef43fb2a34ce96e2a3d1562e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 4 Apr 2021 22:51:21 +0200 Subject: [PATCH 48/96] Do not allow write variant of string as local variable To be consitent with read where it's not allowed. --- components/esm/variantimp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp index aeea5017e..2b69923d1 100644 --- a/components/esm/variantimp.cpp +++ b/components/esm/variantimp.cpp @@ -98,6 +98,9 @@ void ESM::VariantStringData::write (ESMWriter& esm, Variant::Format format, VarT if (format==Variant::Format_Info) throw std::runtime_error ("info variables of type string not supported"); + if (format==Variant::Format_Local) + throw std::runtime_error ("local variables of type string not supported"); + // GMST esm.writeHNString ("STRV", mValue); } From dae3f022baf4f33372176a2b784213d1750ddbd6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 4 Apr 2021 20:15:58 +0200 Subject: [PATCH 49/96] Add tests for ESM::Variant --- apps/openmw_test_suite/CMakeLists.txt | 1 + apps/openmw_test_suite/esm/variant.cpp | 515 +++++++++++++++++++++++++ 2 files changed, 516 insertions(+) create mode 100644 apps/openmw_test_suite/esm/variant.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index a3bb0c6f8..d78cb6955 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -13,6 +13,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) mwdialogue/test_keywordsearch.cpp esm/test_fixed_string.cpp + esm/variant.cpp misc/test_stringops.cpp misc/test_endianness.cpp diff --git a/apps/openmw_test_suite/esm/variant.cpp b/apps/openmw_test_suite/esm/variant.cpp new file mode 100644 index 000000000..c00f73d73 --- /dev/null +++ b/apps/openmw_test_suite/esm/variant.cpp @@ -0,0 +1,515 @@ +#include +#include +#include +#include + +#include +#include + +#include + +namespace +{ + using namespace testing; + using namespace ESM; + + Variant makeVariant(VarType type) + { + Variant v; + v.setType(type); + return v; + } + + Variant makeVariant(VarType type, int value) + { + Variant v; + v.setType(type); + v.setInteger(value); + return v; + } + + TEST(ESMVariantTest, move_constructed_should_have_data) + { + Variant a(int{42}); + const Variant b(std::move(a)); + ASSERT_EQ(b.getInteger(), 42); + } + + TEST(ESMVariantTest, copy_constructed_is_equal_to_source) + { + const Variant a(int{42}); + const Variant b(a); + ASSERT_EQ(a, b); + } + + TEST(ESMVariantTest, copy_constructed_does_not_share_data_with_source) + { + const Variant a(int{42}); + Variant b(a); + b.setInteger(13); + ASSERT_EQ(a.getInteger(), 42); + ASSERT_EQ(b.getInteger(), 13); + } + + TEST(ESMVariantTest, move_assigned_should_have_data) + { + Variant b; + { + Variant a(int{42}); + b = std::move(a); + } + ASSERT_EQ(b.getInteger(), 42); + } + + TEST(ESMVariantTest, copy_assigned_is_equal_to_source) + { + const Variant a(int{42}); + Variant b; + b = a; + ASSERT_EQ(a, b); + } + + TEST(ESMVariantTest, not_equal_is_negation_of_equal) + { + const Variant a(int{42}); + Variant b; + b = a; + ASSERT_TRUE(!(a != b)); + } + + TEST(ESMVariantTest, different_types_are_not_equal) + { + ASSERT_NE(Variant(int{42}), Variant(float{2.7f})); + } + + struct ESMVariantWriteToOStreamTest : TestWithParam> {}; + + TEST_P(ESMVariantWriteToOStreamTest, should_write) + { + const auto [variant, result] = GetParam(); + std::ostringstream s; + s << variant; + ASSERT_EQ(s.str(), result); + } + + INSTANTIATE_TEST_SUITE_P(VariantAsString, ESMVariantWriteToOStreamTest, Values( + std::make_tuple(Variant(), "variant none"), + std::make_tuple(Variant(int{42}), "variant long: 42"), + std::make_tuple(Variant(float{2.7f}), "variant float: 2.7"), + std::make_tuple(Variant(std::string("foo")), "variant string: \"foo\""), + std::make_tuple(makeVariant(VT_Unknown), "variant unknown"), + std::make_tuple(makeVariant(VT_Short, 42), "variant short: 42"), + std::make_tuple(makeVariant(VT_Int, 42), "variant int: 42") + )); + + struct ESMVariantGetTypeTest : Test {}; + + TEST(ESMVariantGetTypeTest, default_constructed_should_return_none) + { + ASSERT_EQ(Variant().getType(), VT_None); + } + + TEST(ESMVariantGetTypeTest, for_constructed_from_int_should_return_long) + { + ASSERT_EQ(Variant(int{}).getType(), VT_Long); + } + + TEST(ESMVariantGetTypeTest, for_constructed_from_float_should_return_float) + { + ASSERT_EQ(Variant(float{}).getType(), VT_Float); + } + + TEST(ESMVariantGetTypeTest, for_constructed_from_lvalue_string_should_return_string) + { + const std::string string; + ASSERT_EQ(Variant(string).getType(), VT_String); + } + + TEST(ESMVariantGetTypeTest, for_constructed_from_rvalue_string_should_return_string) + { + ASSERT_EQ(Variant(std::string{}).getType(), VT_String); + } + + struct ESMVariantGetIntegerTest : Test {}; + + TEST(ESMVariantGetIntegerTest, for_default_constructed_should_throw_exception) + { + ASSERT_THROW(Variant().getInteger(), std::runtime_error); + } + + TEST(ESMVariantGetIntegerTest, for_constructed_from_int_should_return_same_value) + { + const Variant variant(int{42}); + ASSERT_EQ(variant.getInteger(), 42); + } + + TEST(ESMVariantGetIntegerTest, for_constructed_from_float_should_return_casted_to_int) + { + const Variant variant(float{2.7}); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantGetIntegerTest, for_constructed_from_string_should_throw_exception) + { + const Variant variant(std::string("foo")); + ASSERT_THROW(variant.getInteger(), std::runtime_error); + } + + TEST(ESMVariantGetFloatTest, for_default_constructed_should_throw_exception) + { + ASSERT_THROW(Variant().getFloat(), std::runtime_error); + } + + TEST(ESMVariantGetFloatTest, for_constructed_from_int_should_return_casted_to_float) + { + const Variant variant(int{42}); + ASSERT_EQ(variant.getFloat(), 42); + } + + TEST(ESMVariantGetFloatTest, for_constructed_from_float_should_return_same_value) + { + const Variant variant(float{2.7f}); + ASSERT_EQ(variant.getFloat(), 2.7f); + } + + TEST(ESMVariantGetFloatTest, for_constructed_from_string_should_throw_exception) + { + const Variant variant(std::string("foo")); + ASSERT_THROW(variant.getFloat(), std::runtime_error); + } + + TEST(ESMVariantGetStringTest, for_default_constructed_should_throw_exception) + { + ASSERT_THROW(Variant().getString(), std::runtime_error); + } + + TEST(ESMVariantGetStringTest, for_constructed_from_int_should_throw_exception) + { + const Variant variant(int{42}); + ASSERT_THROW(variant.getString(), std::runtime_error); + } + + TEST(ESMVariantGetStringTest, for_constructed_from_float_should_throw_exception) + { + const Variant variant(float{2.7}); + ASSERT_THROW(variant.getString(), std::runtime_error); + } + + TEST(ESMVariantGetStringTest, for_constructed_from_string_should_return_same_value) + { + const Variant variant(std::string("foo")); + ASSERT_EQ(variant.getString(), "foo"); + } + + TEST(ESMVariantSetTypeTest, for_unknown_should_reset_data) + { + Variant variant(int{42}); + variant.setType(VT_Unknown); + ASSERT_THROW(variant.getInteger(), std::runtime_error); + } + + TEST(ESMVariantSetTypeTest, for_none_should_reset_data) + { + Variant variant(int{42}); + variant.setType(VT_None); + ASSERT_THROW(variant.getInteger(), std::runtime_error); + } + + TEST(ESMVariantSetTypeTest, for_same_type_should_not_change_value) + { + Variant variant(int{42}); + variant.setType(VT_Long); + ASSERT_EQ(variant.getInteger(), 42); + } + + TEST(ESMVariantSetTypeTest, for_float_replaced_by_int_should_cast_float_to_int) + { + Variant variant(float{2.7f}); + variant.setType(VT_Int); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetTypeTest, for_string_replaced_by_int_should_set_default_initialized_data) + { + Variant variant(std::string("foo")); + variant.setType(VT_Int); + ASSERT_EQ(variant.getInteger(), 0); + } + + TEST(ESMVariantSetTypeTest, for_default_constructed_replaced_by_float_should_set_default_initialized_value) + { + Variant variant; + variant.setType(VT_Float); + ASSERT_EQ(variant.getInteger(), 0.0f); + } + + TEST(ESMVariantSetTypeTest, for_float_replaced_by_short_should_cast_data_to_int) + { + Variant variant(float{2.7f}); + variant.setType(VT_Short); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetTypeTest, for_float_replaced_by_long_should_cast_data_to_int) + { + Variant variant(float{2.7f}); + variant.setType(VT_Long); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetTypeTest, for_int_replaced_by_float_should_cast_data_to_float) + { + Variant variant(int{42}); + variant.setType(VT_Float); + ASSERT_EQ(variant.getFloat(), 42.0f); + } + + TEST(ESMVariantSetTypeTest, for_int_replaced_by_string_should_set_default_initialized_data) + { + Variant variant(int{42}); + variant.setType(VT_String); + ASSERT_EQ(variant.getString(), ""); + } + + TEST(ESMVariantSetIntegerTest, for_default_constructed_should_throw_exception) + { + Variant variant; + ASSERT_THROW(variant.setInteger(42), std::runtime_error); + } + + TEST(ESMVariantSetIntegerTest, for_unknown_should_throw_exception) + { + Variant variant; + variant.setType(VT_Unknown); + ASSERT_THROW(variant.setInteger(42), std::runtime_error); + } + + TEST(ESMVariantSetIntegerTest, for_default_int_should_change_value) + { + Variant variant(int{13}); + variant.setInteger(42); + ASSERT_EQ(variant.getInteger(), 42); + } + + TEST(ESMVariantSetIntegerTest, for_int_should_change_value) + { + Variant variant; + variant.setType(VT_Int); + variant.setInteger(42); + ASSERT_EQ(variant.getInteger(), 42); + } + + TEST(ESMVariantSetIntegerTest, for_short_should_change_value) + { + Variant variant; + variant.setType(VT_Short); + variant.setInteger(42); + ASSERT_EQ(variant.getInteger(), 42); + } + + TEST(ESMVariantSetIntegerTest, for_float_should_change_value) + { + Variant variant(float{2.7f}); + variant.setInteger(42); + ASSERT_EQ(variant.getFloat(), 42.0f); + } + + TEST(ESMVariantSetIntegerTest, for_string_should_throw_exception) + { + Variant variant(std::string{}); + ASSERT_THROW(variant.setInteger(42), std::runtime_error); + } + + TEST(ESMVariantSetFloatTest, for_default_constructed_should_throw_exception) + { + Variant variant; + ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error); + } + + TEST(ESMVariantSetFloatTest, for_unknown_should_throw_exception) + { + Variant variant; + variant.setType(VT_Unknown); + ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error); + } + + TEST(ESMVariantSetFloatTest, for_default_int_should_change_value) + { + Variant variant(int{13}); + variant.setFloat(2.7f); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetFloatTest, for_int_should_change_value) + { + Variant variant; + variant.setType(VT_Int); + variant.setFloat(2.7f); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetFloatTest, for_short_should_change_value) + { + Variant variant; + variant.setType(VT_Short); + variant.setFloat(2.7f); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetFloatTest, for_float_should_change_value) + { + Variant variant(float{2.7f}); + variant.setFloat(3.14f); + ASSERT_EQ(variant.getFloat(), 3.14f); + } + + TEST(ESMVariantSetFloatTest, for_string_should_throw_exception) + { + Variant variant(std::string{}); + ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_default_constructed_should_throw_exception) + { + Variant variant; + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_unknown_should_throw_exception) + { + Variant variant; + variant.setType(VT_Unknown); + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_default_int_should_throw_exception) + { + Variant variant(int{13}); + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_int_should_throw_exception) + { + Variant variant; + variant.setType(VT_Int); + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_short_should_throw_exception) + { + Variant variant; + variant.setType(VT_Short); + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_float_should_throw_exception) + { + Variant variant(float{2.7f}); + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_string_should_change_value) + { + Variant variant(std::string("foo")); + variant.setString("bar"); + ASSERT_EQ(variant.getString(), "bar"); + } + + struct WriteToESMTestCase + { + Variant mVariant; + Variant::Format mFormat; + std::size_t mDataSize {}; + std::size_t mDataHash {}; + }; + + std::string write(const Variant& variant, const Variant::Format format) + { + std::ostringstream out; + ESM::ESMWriter writer; + writer.save(out); + variant.write(writer, format); + writer.close(); + return out.str(); + } + + Variant read(const Variant::Format format, const std::string& data) + { + Variant result; + ESM::ESMReader reader; + reader.open(std::make_shared(data), ""); + result.read(reader, format); + return result; + } + + Variant writeAndRead(const Variant& variant, const Variant::Format format, std::size_t dataSize, std::size_t dataHash) + { + const std::string data = write(variant, format); + EXPECT_EQ(data.size(), dataSize); + EXPECT_EQ(std::hash{}(data), dataHash); + return read(format, data); + } + + struct ESMVariantToESMTest : TestWithParam {}; + + TEST_P(ESMVariantToESMTest, deserialized_is_equal_to_serialized) + { + const auto param = GetParam(); + const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize, param.mDataHash); + ASSERT_EQ(param.mVariant, result); + } + + INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMTest, Values( + WriteToESMTestCase {Variant(), Variant::Format_Gmst, 324, 10398667754238537314ul}, + WriteToESMTestCase {Variant(int{42}), Variant::Format_Global, 345, 2440845426097842853ul}, + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Global, 345, 8428720798053904009ul}, + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Info, 336, 11930997575130354755ul}, + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Local, 336, 11930997575130354755ul}, + WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Global, 345, 7812065815960720679ul}, + WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Local, 334, 5017869102981712080ul}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Info, 336, 12560431547347287906ul}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Local, 336, 12560431547347287906ul} + )); + + struct ESMVariantToESMNoneTest : TestWithParam {}; + + TEST_P(ESMVariantToESMNoneTest, deserialized_is_none) + { + const auto param = GetParam(); + const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize, param.mDataHash); + ASSERT_EQ(Variant(), result); + } + + INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMNoneTest, Values( + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Gmst, 336, 11930997575130354755ul}, + WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Gmst, 335, 7604528240659685057ul}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Gmst, 336, 12560431547347287906ul} + )); + + struct ESMVariantWriteToESMFailTest : TestWithParam {}; + + TEST_P(ESMVariantWriteToESMFailTest, write_is_not_supported) + { + const auto param = GetParam(); + std::ostringstream out; + ESM::ESMWriter writer; + writer.save(out); + ASSERT_THROW(param.mVariant.write(writer, param.mFormat), std::runtime_error); + } + + INSTANTIATE_TEST_SUITE_P(VariantAndFormat, ESMVariantWriteToESMFailTest, Values( + WriteToESMTestCase {Variant(), Variant::Format_Global}, + WriteToESMTestCase {Variant(), Variant::Format_Info}, + WriteToESMTestCase {Variant(), Variant::Format_Local}, + WriteToESMTestCase {Variant(int{42}), Variant::Format_Gmst}, + WriteToESMTestCase {Variant(int{42}), Variant::Format_Info}, + WriteToESMTestCase {Variant(int{42}), Variant::Format_Local}, + WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Global}, + WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Info}, + WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Local}, + WriteToESMTestCase {makeVariant(VT_Unknown), Variant::Format_Global}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Global}, + WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Gmst}, + WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Info} + )); +} From ecde3932e273f34fb6f07ff7396a3072245540db Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 5 Apr 2021 09:43:37 +0200 Subject: [PATCH 50/96] Make AddItem's quantity overflow on negative numbers --- CHANGELOG.md | 1 + apps/openmw/mwscript/containerextensions.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1fea773e..f8cb55d36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,7 @@ Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Bug #5912: ImprovedBound mod doesn't work Bug #5914: BM: The Swimmer can't reach destination + Bug #5934: AddItem command doesn't accept negative values Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 186940dd9..375242f17 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -93,7 +93,7 @@ namespace MWScript runtime.pop(); if (count<0) - throw std::runtime_error ("second argument for AddItem must be non-negative"); + count = static_cast(count); // no-op if (count == 0) From 045bb7cbd7d206f9de9e4b624aa6dd6dc0d4d55a Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Apr 2021 00:48:35 +0200 Subject: [PATCH 51/96] Store CustomData and ContainerStore as unique_ptr --- apps/openmw/mwclass/container.cpp | 8 +++--- apps/openmw/mwclass/container.hpp | 2 +- apps/openmw/mwclass/creature.cpp | 36 ++++++++++++++----------- apps/openmw/mwclass/creaturelevlist.cpp | 10 +++---- apps/openmw/mwclass/door.cpp | 9 +++---- apps/openmw/mwclass/npc.cpp | 11 ++++---- apps/openmw/mwworld/containerstore.hpp | 2 +- apps/openmw/mwworld/customdata.hpp | 4 ++- apps/openmw/mwworld/inventorystore.hpp | 2 +- apps/openmw/mwworld/refdata.cpp | 11 +++----- apps/openmw/mwworld/refdata.hpp | 5 ++-- 11 files changed, 52 insertions(+), 48 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 28305c394..2d7f30047 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -46,9 +46,9 @@ namespace MWClass mStore.readState(inventory); } - MWWorld::CustomData *ContainerCustomData::clone() const + std::unique_ptr ContainerCustomData::clone() const { - return new ContainerCustomData (*this); + return std::make_unique(*this); } ContainerCustomData& ContainerCustomData::asContainerCustomData() @@ -72,7 +72,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); // store - ptr.getRefData().setCustomData (std::make_unique(*ref->mBase, ptr.getCell()).release()); + ptr.getRefData().setCustomData (std::make_unique(*ref->mBase, ptr.getCell())); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); } @@ -317,7 +317,7 @@ namespace MWClass return; const ESM::ContainerState& containerState = state.asContainerState(); - ptr.getRefData().setCustomData(std::make_unique(containerState.mInventory).release()); + ptr.getRefData().setCustomData(std::make_unique(containerState.mInventory)); } void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 2dc0c06ca..fc3e046f1 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -20,7 +20,7 @@ namespace MWClass ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell); ContainerCustomData(const ESM::InventoryState& inventory); - MWWorld::CustomData *clone() const override; + std::unique_ptr clone() const override; ContainerCustomData& asContainerCustomData() override; const ContainerCustomData& asContainerCustomData() const override; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 31341db73..2a55fb9fd 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -55,10 +55,14 @@ namespace MWClass { public: MWMechanics::CreatureStats mCreatureStats; - MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures + std::unique_ptr mContainerStore; // may be InventoryStore for some creatures MWMechanics::Movement mMovement; - MWWorld::CustomData *clone() const override; + CreatureCustomData() = default; + CreatureCustomData(const CreatureCustomData& other); + CreatureCustomData(CreatureCustomData&& other) noexcept = default; + + std::unique_ptr clone() const override; CreatureCustomData& asCreatureCustomData() override { @@ -68,16 +72,18 @@ namespace MWClass { return *this; } - - CreatureCustomData() : mContainerStore(nullptr) {} - virtual ~CreatureCustomData() { delete mContainerStore; } }; - MWWorld::CustomData *CreatureCustomData::clone() const + CreatureCustomData::CreatureCustomData(const CreatureCustomData& other) + : mCreatureStats(other.mCreatureStats), + mContainerStore(other.mContainerStore->clone()), + mMovement(other.mMovement) + { + } + + std::unique_ptr CreatureCustomData::clone() const { - CreatureCustomData* cloned = new CreatureCustomData (*this); - cloned->mContainerStore = mContainerStore->clone(); - return cloned; + return std::make_unique(*this); } const Creature::GMST& Creature::getGmst() @@ -148,16 +154,16 @@ namespace MWClass // inventory bool hasInventory = hasInventoryStore(ptr); if (hasInventory) - data->mContainerStore = new MWWorld::InventoryStore(); + data->mContainerStore = std::make_unique(); else - data->mContainerStore = new MWWorld::ContainerStore(); + data->mContainerStore = std::make_unique(); data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); data->mCreatureStats.setNeedRecalcDynamicStats(false); // store - ptr.getRefData().setCustomData(data.release()); + ptr.getRefData().setCustomData(std::move(data)); getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); @@ -758,11 +764,11 @@ namespace MWClass std::unique_ptr data (new CreatureCustomData); if (hasInventoryStore(ptr)) - data->mContainerStore = new MWWorld::InventoryStore(); + data->mContainerStore = std::make_unique(); else - data->mContainerStore = new MWWorld::ContainerStore(); + data->mContainerStore = std::make_unique(); - ptr.getRefData().setCustomData (data.release()); + ptr.getRefData().setCustomData (std::move(data)); } } else diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index e3e52901e..3b401f1a3 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -17,7 +17,7 @@ namespace MWClass int mSpawnActorId; bool mSpawn; // Should a new creature be spawned? - MWWorld::CustomData *clone() const override; + std::unique_ptr clone() const override; CreatureLevListCustomData& asCreatureLevListCustomData() override { @@ -29,9 +29,9 @@ namespace MWClass } }; - MWWorld::CustomData *CreatureLevListCustomData::clone() const + std::unique_ptr CreatureLevListCustomData::clone() const { - return new CreatureLevListCustomData (*this); + return std::make_unique(*this); } std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const @@ -138,11 +138,11 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - std::unique_ptr data (new CreatureLevListCustomData); + std::unique_ptr data = std::make_unique(); data->mSpawnActorId = -1; data->mSpawn = true; - ptr.getRefData().setCustomData(data.release()); + ptr.getRefData().setCustomData(std::move(data)); } } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 25f7fc456..ae8085586 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -36,7 +36,7 @@ namespace MWClass public: MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle; - MWWorld::CustomData *clone() const override; + std::unique_ptr clone() const override; DoorCustomData& asDoorCustomData() override { @@ -48,9 +48,9 @@ namespace MWClass } }; - MWWorld::CustomData *DoorCustomData::clone() const + std::unique_ptr DoorCustomData::clone() const { - return new DoorCustomData (*this); + return std::make_unique(*this); } void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const @@ -327,8 +327,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - std::unique_ptr data(new DoorCustomData); - ptr.getRefData().setCustomData(data.release()); + ptr.getRefData().setCustomData(std::make_unique()); } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 5de4c197e..248ef5d01 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -253,7 +253,7 @@ namespace MWClass MWMechanics::Movement mMovement; MWWorld::InventoryStore mInventoryStore; - MWWorld::CustomData *clone() const override; + std::unique_ptr clone() const override; NpcCustomData& asNpcCustomData() override { @@ -265,9 +265,9 @@ namespace MWClass } }; - MWWorld::CustomData *NpcCustomData::clone() const + std::unique_ptr NpcCustomData::clone() const { - return new NpcCustomData (*this); + return std::make_unique(*this); } const Npc::GMST& Npc::getGmst() @@ -397,7 +397,7 @@ namespace MWClass data->mNpcStats.setGoldPool(gold); // store - ptr.getRefData().setCustomData (data.release()); + ptr.getRefData().setCustomData(std::move(data)); getInventoryStore(ptr).autoEquip(ptr); } @@ -1302,8 +1302,7 @@ namespace MWClass if (!ptr.getRefData().getCustomData()) { // Create a CustomData, but don't fill it from ESM records (not needed) - std::unique_ptr data (new NpcCustomData); - ptr.getRefData().setCustomData (data.release()); + ptr.getRefData().setCustomData(std::make_unique()); } } else diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 882f5efc4..bb8ca5273 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -153,7 +153,7 @@ namespace MWWorld virtual ~ContainerStore(); - virtual ContainerStore* clone() { return new ContainerStore(*this); } + virtual std::unique_ptr clone() { return std::make_unique(*this); } ConstContainerStoreIterator cbegin (int mask = Type_All) const; ConstContainerStoreIterator cend() const; diff --git a/apps/openmw/mwworld/customdata.hpp b/apps/openmw/mwworld/customdata.hpp index 8af45e36a..ee89dd2a8 100644 --- a/apps/openmw/mwworld/customdata.hpp +++ b/apps/openmw/mwworld/customdata.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWWORLD_CUSTOMDATA_H #define GAME_MWWORLD_CUSTOMDATA_H +#include + namespace MWClass { class CreatureCustomData; @@ -19,7 +21,7 @@ namespace MWWorld virtual ~CustomData() {} - virtual CustomData *clone() const = 0; + virtual std::unique_ptr clone() const = 0; // Fast version of dynamic_cast. Needs to be overridden in the respective class. diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 6809e63b2..32dc0d2e9 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -123,7 +123,7 @@ namespace MWWorld InventoryStore& operator= (const InventoryStore& store); - InventoryStore* clone() override { return new InventoryStore(*this); } + std::unique_ptr clone() override { return std::make_unique(*this); } ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true) override; ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 71ff6d040..f6a445d61 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -40,8 +40,6 @@ namespace MWWorld void RefData::cleanup() { mBaseNode = nullptr; - - delete mCustomData; mCustomData = nullptr; } @@ -223,21 +221,20 @@ namespace MWWorld return mPosition; } - void RefData::setCustomData (CustomData *data) + void RefData::setCustomData(std::unique_ptr&& value) noexcept { mChanged = true; // We do not currently track CustomData, so assume anything with a CustomData is changed - delete mCustomData; - mCustomData = data; + mCustomData = std::move(value); } CustomData *RefData::getCustomData() { - return mCustomData; + return mCustomData.get(); } const CustomData *RefData::getCustomData() const { - return mCustomData; + return mCustomData.get(); } bool RefData::hasChanged() const diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 738a6d53a..5419a261b 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -7,6 +7,7 @@ #include "../mwscript/locals.hpp" #include +#include namespace SceneUtil { @@ -44,7 +45,7 @@ namespace MWWorld ESM::AnimationState mAnimationState; - CustomData *mCustomData; + std::unique_ptr mCustomData; void copy (const RefData& refData); @@ -117,7 +118,7 @@ namespace MWWorld void setPosition (const ESM::Position& pos); const ESM::Position& getPosition() const; - void setCustomData (CustomData *data); + void setCustomData(std::unique_ptr&& value) noexcept; ///< Set custom data (potentially replacing old custom data). The ownership of \a data is /// transferred to this. From e380470558a1d810d7e5cd51c09a31ace376c1e6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Apr 2021 00:49:14 +0200 Subject: [PATCH 52/96] Add move ctor and assignment operator to RefData --- apps/openmw/mwworld/refdata.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 5419a261b..8979c8505 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -5,6 +5,7 @@ #include #include "../mwscript/locals.hpp" +#include "../mwworld/customdata.hpp" #include #include @@ -69,6 +70,7 @@ namespace MWWorld /// perform these operations). RefData (const RefData& refData); + RefData (RefData&& other) noexcept = default; ~RefData(); @@ -77,6 +79,7 @@ namespace MWWorld /// perform this operations). RefData& operator= (const RefData& refData); + RefData& operator= (RefData&& other) noexcept = default; /// Return base node (can be a null pointer). SceneUtil::PositionAttitudeTransform* getBaseNode(); From bd33fa76b63536398e00ba416d3289a44beaf461 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Apr 2021 12:59:44 +0200 Subject: [PATCH 53/96] Use CRTP to define CustomData clone function --- apps/openmw/mwclass/container.cpp | 5 ----- apps/openmw/mwclass/container.hpp | 4 +--- apps/openmw/mwclass/creature.cpp | 9 +-------- apps/openmw/mwclass/creaturelevlist.cpp | 9 +-------- apps/openmw/mwclass/door.cpp | 9 +-------- apps/openmw/mwclass/npc.cpp | 9 +-------- apps/openmw/mwworld/customdata.hpp | 9 +++++++++ 7 files changed, 14 insertions(+), 40 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 2d7f30047..de560608c 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -46,11 +46,6 @@ namespace MWClass mStore.readState(inventory); } - std::unique_ptr ContainerCustomData::clone() const - { - return std::make_unique(*this); - } - ContainerCustomData& ContainerCustomData::asContainerCustomData() { return *this; diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index fc3e046f1..1c8937006 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -13,15 +13,13 @@ namespace ESM namespace MWClass { - class ContainerCustomData : public MWWorld::CustomData + class ContainerCustomData : public MWWorld::TypedCustomData { MWWorld::ContainerStore mStore; public: ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell); ContainerCustomData(const ESM::InventoryState& inventory); - std::unique_ptr clone() const override; - ContainerCustomData& asContainerCustomData() override; const ContainerCustomData& asContainerCustomData() const override; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2a55fb9fd..6b8d2f3f2 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -51,7 +51,7 @@ namespace namespace MWClass { - class CreatureCustomData : public MWWorld::CustomData + class CreatureCustomData : public MWWorld::TypedCustomData { public: MWMechanics::CreatureStats mCreatureStats; @@ -62,8 +62,6 @@ namespace MWClass CreatureCustomData(const CreatureCustomData& other); CreatureCustomData(CreatureCustomData&& other) noexcept = default; - std::unique_ptr clone() const override; - CreatureCustomData& asCreatureCustomData() override { return *this; @@ -81,11 +79,6 @@ namespace MWClass { } - std::unique_ptr CreatureCustomData::clone() const - { - return std::make_unique(*this); - } - const Creature::GMST& Creature::getGmst() { static GMST gmst; diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 3b401f1a3..f86004c61 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -10,15 +10,13 @@ namespace MWClass { - class CreatureLevListCustomData : public MWWorld::CustomData + class CreatureLevListCustomData : public MWWorld::TypedCustomData { public: // actorId of the creature we spawned int mSpawnActorId; bool mSpawn; // Should a new creature be spawned? - std::unique_ptr clone() const override; - CreatureLevListCustomData& asCreatureLevListCustomData() override { return *this; @@ -29,11 +27,6 @@ namespace MWClass } }; - std::unique_ptr CreatureLevListCustomData::clone() const - { - return std::make_unique(*this); - } - std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const { return ""; diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ae8085586..3a5ff0d9a 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -31,13 +31,11 @@ namespace MWClass { - class DoorCustomData : public MWWorld::CustomData + class DoorCustomData : public MWWorld::TypedCustomData { public: MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle; - std::unique_ptr clone() const override; - DoorCustomData& asDoorCustomData() override { return *this; @@ -48,11 +46,6 @@ namespace MWClass } }; - std::unique_ptr DoorCustomData::clone() const - { - return std::make_unique(*this); - } - void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 248ef5d01..c5b352cb4 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -246,15 +246,13 @@ namespace namespace MWClass { - class NpcCustomData : public MWWorld::CustomData + class NpcCustomData : public MWWorld::TypedCustomData { public: MWMechanics::NpcStats mNpcStats; MWMechanics::Movement mMovement; MWWorld::InventoryStore mInventoryStore; - std::unique_ptr clone() const override; - NpcCustomData& asNpcCustomData() override { return *this; @@ -265,11 +263,6 @@ namespace MWClass } }; - std::unique_ptr NpcCustomData::clone() const - { - return std::make_unique(*this); - } - const Npc::GMST& Npc::getGmst() { static GMST gmst; diff --git a/apps/openmw/mwworld/customdata.hpp b/apps/openmw/mwworld/customdata.hpp index ee89dd2a8..7200e7684 100644 --- a/apps/openmw/mwworld/customdata.hpp +++ b/apps/openmw/mwworld/customdata.hpp @@ -40,6 +40,15 @@ namespace MWWorld virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData(); virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const; }; + + template + struct TypedCustomData : CustomData + { + std::unique_ptr clone() const final + { + return std::make_unique(*static_cast(this)); + } + }; } #endif From 59720aea9a2eeed93e47974016a11012fd01643b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 7 Apr 2021 12:07:03 +0400 Subject: [PATCH 54/96] Restore old aiming for melee combat --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/aicombat.cpp | 6 +++--- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 10 ++++------ apps/openmw/mwworld/worldimp.hpp | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 5e844ffae..3d55ad987 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -607,7 +607,7 @@ namespace MWBase /// Return a vector aiming the actor's weapon towards a target. /// @note The length of the vector is the distance between actor and target. - virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; + virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) = 0; /// Return the distance between actor's weapon and target's collision box. virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 51fcb92c1..5edd59ded 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -228,7 +228,6 @@ namespace MWMechanics const osg::Vec3f vActorPos(pos.asVec3()); const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); - osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS); @@ -236,13 +235,14 @@ namespace MWMechanics if (isRangedCombat) { // rotate actor taking into account target movement direction and projectile speed - vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); + osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); } else { + osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } @@ -698,7 +698,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3(); - osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); + osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, true); float distToTarget = vDirToTarget.length(); osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 8c58fd943..b83d313ce 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2955,7 +2955,7 @@ void CharacterController::updateHeadTracking(float duration) } else // no head node to look at, fall back to look at center of collision box - direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget); + direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget, false); } direction.normalize(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5e0cdbb2b..3b50dd6aa 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3885,14 +3885,12 @@ namespace MWWorld return false; } - osg::Vec3f World::aimToTarget(const ConstPtr &actor, const ConstPtr &target) + osg::Vec3f World::aimToTarget(const ConstPtr &actor, const ConstPtr &target, bool isRangedCombat) { osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); - osg::Vec3f weaponHalfExtents = mPhysics->getHalfExtents(actor); - osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); - osg::Vec3f targetHalfExtents = mPhysics->getHalfExtents(target); - weaponPos.z() += weaponHalfExtents.z() * 2 * Constants::TorsoHeight; - targetPos.z() += targetHalfExtents.z(); + float heightRatio = isRangedCombat ? 2.f * Constants::TorsoHeight : 1.f; + weaponPos.z() += mPhysics->getHalfExtents(actor).z() * heightRatio; + osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); return (targetPos - weaponPos); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index e2d344930..929e035e9 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -705,7 +705,7 @@ namespace MWWorld /// Return a vector aiming the actor's weapon towards a target. /// @note The length of the vector is the distance between actor and target. - osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) override; + osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) override; /// Return the distance between actor's weapon and target's collision box. float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) override; From 44f2cb092346e9588e4e9d943548afaa65c90ee4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 7 Apr 2021 17:30:21 +0200 Subject: [PATCH 55/96] Fix targeted scripts losing their targets when rearranging your load order --- apps/openmw/mwscript/globalscripts.cpp | 9 ++++++++- apps/openmw/mwscript/globalscripts.hpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 1a7e3ebbc..0d579abdc 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -235,13 +235,20 @@ namespace MWScript } } - bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type) + bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) { if (type==ESM::REC_GSCR) { ESM::GlobalScript script; script.load (reader); + if (script.mTargetRef.hasContentFile()) + { + auto iter = contentFileMap.find(script.mTargetRef.mContentFile); + if (iter != contentFileMap.end()) + script.mTargetRef.mContentFile = iter->second; + } + auto iter = mScripts.find (script.mId); if (iter==mScripts.end()) diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index c5c5a9a45..049e78804 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -73,7 +73,7 @@ namespace MWScript void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - bool readRecord (ESM::ESMReader& reader, uint32_t type); + bool readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap); ///< Records for variables that do not exist are dropped silently. /// /// \return Known type? diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 93f75c6f1..fb418b94a 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -461,7 +461,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_GSCR: - MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval, contentFileMap); break; case ESM::REC_GMAP: From 3ad2335d115c7a4baa2290554ed998651c072cb9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 7 Apr 2021 20:23:39 +0000 Subject: [PATCH 56/96] Fix FetchContent Bullet with MSVC --- extern/CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index fc00ae254..3842b8357 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -35,9 +35,13 @@ if(NOT OPENMW_USE_SYSTEM_BULLET) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) else() set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) - if(MSVC) - set(USE_MSVC_RUNTIME_LIBRARY_DLL ON CACHE BOOL "" FORCE) - endif() + endif() + + if(MSVC) + # this setting is badly named - having it off forces the static runtime library, + # but having it on does nothing, letting the defaults get used. + # OpenMW uses the defaults, and you can't mix and match. + set(USE_MSVC_RUNTIME_LIBRARY_DLL ON CACHE BOOL "" FORCE) endif() # master on 12 Mar 2021 From d617d66a87fb58524f6a330b2c9e73559af192a0 Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Wed, 7 Apr 2021 18:58:46 +0200 Subject: [PATCH 57/96] Add file to BSA --- apps/bsatool/bsatool.cpp | 43 +++++++- components/bsa/bsa_file.cpp | 156 ++++++++++++++++++++++++++- components/bsa/bsa_file.hpp | 41 +++++-- components/bsa/compressedbsafile.cpp | 19 ++-- components/bsa/compressedbsafile.hpp | 2 +- components/vfs/bsaarchive.cpp | 4 +- 6 files changed, 238 insertions(+), 27 deletions(-) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index 3afbd777f..8e8cf8918 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -20,6 +20,7 @@ struct Arguments std::string mode; std::string filename; std::string extractfile; + std::string addfile; std::string outdir; bool longformat; @@ -36,6 +37,10 @@ bool parseOptions (int argc, char** argv, Arguments &info) " Extract a file from the input archive.\n\n" " bsatool extractall archivefile [output_directory]\n" " Extract all files from the input archive.\n\n" + " bsatool add [-a] archivefile file_to_add\n" + " Add a file to the input archive.\n\n" + " bsatool create [-c] archivefile\n" + " Create an archive.\n\n" "Allowed options"); desc.add_options() @@ -95,7 +100,7 @@ bool parseOptions (int argc, char** argv, Arguments &info) } info.mode = variables["mode"].as(); - if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall")) + if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall" || info.mode == "add" || info.mode == "create")) { std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n" << desc << std::endl; @@ -126,6 +131,17 @@ bool parseOptions (int argc, char** argv, Arguments &info) if (variables["input-file"].as< std::vector >().size() > 2) info.outdir = variables["input-file"].as< std::vector >()[2]; } + else if (info.mode == "add") + { + if (variables["input-file"].as< std::vector >().size() < 1) + { + std::cout << "\nERROR: file to add unspecified\n\n" + << desc << std::endl; + return false; + } + if (variables["input-file"].as< std::vector >().size() > 1) + info.addfile = variables["input-file"].as< std::vector >()[1]; + } else if (variables["input-file"].as< std::vector >().size() > 1) info.outdir = variables["input-file"].as< std::vector >()[1]; @@ -138,6 +154,7 @@ bool parseOptions (int argc, char** argv, Arguments &info) int list(std::unique_ptr& bsa, Arguments& info); int extract(std::unique_ptr& bsa, Arguments& info); int extractAll(std::unique_ptr& bsa, Arguments& info); +int add(std::unique_ptr& bsa, Arguments& info); int main(int argc, char** argv) { @@ -157,6 +174,12 @@ int main(int argc, char** argv) else bsa = std::make_unique(Bsa::BSAFile()); + if (info.mode == "create") + { + bsa->open(info.filename); + return 0; + } + bsa->open(info.filename); if (info.mode == "list") @@ -165,6 +188,8 @@ int main(int argc, char** argv) return extract(bsa, info); else if (info.mode == "extractall") return extractAll(bsa, info); + else if (info.mode == "add") + return add(bsa, info); else { std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; @@ -188,13 +213,13 @@ int list(std::unique_ptr& bsa, Arguments& info) { // Long format std::ios::fmtflags f(std::cout.flags()); - std::cout << std::setw(50) << std::left << file.name; + std::cout << std::setw(50) << std::left << file.name(); std::cout << std::setw(8) << std::left << std::dec << file.fileSize; std::cout << "@ 0x" << std::hex << file.offset << std::endl; std::cout.flags(f); } else - std::cout << file.name << std::endl; + std::cout << file.name() << std::endl; } return 0; @@ -253,7 +278,7 @@ int extractAll(std::unique_ptr& bsa, Arguments& info) { for (const auto &file : bsa->getList()) { - std::string extractPath(file.name); + std::string extractPath(file.name()); Misc::StringUtils::replaceAll(extractPath, "\\", "/"); // Get the target path (the path the file will be extracted to) @@ -272,7 +297,7 @@ int extractAll(std::unique_ptr& bsa, Arguments& info) // Get a stream for the file to extract // (inefficient because getFile iter on the list again) - Files::IStreamPtr data = bsa->getFile(file.name); + Files::IStreamPtr data = bsa->getFile(file.name()); bfs::ofstream out(target, std::ios::binary); // Write the file to disk @@ -283,3 +308,11 @@ int extractAll(std::unique_ptr& bsa, Arguments& info) return 0; } + +int add(std::unique_ptr& bsa, Arguments& info) +{ + boost::filesystem::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in); + bsa->addFile(info.addfile, stream); + + return 0; +} diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index f6220b7ce..ef49a60d2 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -27,6 +27,7 @@ #include #include +#include using namespace Bsa; @@ -37,6 +38,31 @@ void BSAFile::fail(const std::string &msg) throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename); } +//the getHash code is from bsapack from ghostwheel +//the code is also the same as in https://github.com/arviceblot/bsatool_rs/commit/67cb59ec3aaeedc0849222ea387f031c33e48c81 +BSAFile::Hash getHash(const std::string& name) +{ + BSAFile::Hash hash; + unsigned l = (name.size() >> 1); + unsigned sum, off, temp, i, n; + + for (sum = off = i = 0; i < l; i++) { + sum ^= (((unsigned)(name[i])) << (off & 0x1F)); + off += 8; + } + hash.low = sum; + + for (sum = off = 0; i < name.size(); i++) { + temp = (((unsigned)(name[i])) << (off & 0x1F)); + sum ^= temp; + n = temp & 0x1F; + sum = (sum << (32 - n)) | (sum >> n); // binary "rotate right" + off += 8; + } + hash.high = sum; + return hash; +} + /// Read header information from the input source void BSAFile::readHeader() { @@ -113,14 +139,17 @@ void BSAFile::readHeader() // Read the offset info into a temporary buffer std::vector offsets(3*filenum); - input.read(reinterpret_cast(&offsets[0]), 12*filenum); + input.read(reinterpret_cast(offsets.data()), 12*filenum); // Read the string table mStringBuf.resize(dirsize-12*filenum); - input.read(&mStringBuf[0], mStringBuf.size()); + input.read(mStringBuf.data(), mStringBuf.size()); // Check our position assert(input.tellg() == std::streampos(12+dirsize)); + std::vector hashes(filenum); + static_assert(sizeof(Hash) == 8); + input.read(reinterpret_cast(hashes.data()), 8*filenum); // Calculate the offset of the data buffer. All file offsets are // relative to this. 12 header bytes + directory + hash table @@ -129,23 +158,72 @@ void BSAFile::readHeader() // Set up the the FileStruct table mFiles.resize(filenum); + size_t endOfNameBuffer = 0; for(size_t i=0;i fsize) fail("Archive contains offsets outside itself"); // Add the file name to the lookup - mLookup[fs.name] = i; + mLookup[fs.name()] = i; } + mStringBuf.resize(endOfNameBuffer); + + std::sort(mFiles.begin(), mFiles.end(), [](const FileStruct& left, const FileStruct& right) { + return left.offset < right.offset; + }); mIsLoaded = true; } +/// Write header information to the output sink +void Bsa::BSAFile::writeHeader() +{ + namespace bfs = boost::filesystem; + bfs::fstream output(mFilename, std::ios::binary | std::ios::in | std::ios::out); + + uint32_t head[3]; + head[0] = 0x100; + auto fileDataOffset = mFiles.empty() ? 12 : mFiles.front().offset; + head[1] = fileDataOffset - 12 - 8*mFiles.size(); + + output.seekp(0, std::ios_base::end); + + head[2] = mFiles.size(); + output.seekp(0); + output.write(reinterpret_cast(head), 12); + + std::sort(mFiles.begin(), mFiles.end(), [](const FileStruct& left, const FileStruct& right) { + return std::make_pair(left.hash.low, left.hash.high) < std::make_pair(right.hash.low, right.hash.high); + }); + + size_t filenum = mFiles.size(); + std::vector offsets(3* filenum); + std::vector hashes(filenum); + for(size_t i=0;i(offsets.data()), sizeof(uint32_t)*offsets.size()); + output.write(reinterpret_cast(mStringBuf.data()), mStringBuf.size()); + output.seekp(fileDataOffset - 8*mFiles.size(), std::ios_base::beg); + output.write(reinterpret_cast(hashes.data()), sizeof(Hash)*hashes.size()); +} + /// Get the index of a given file name, or -1 if not found int BSAFile::getIndex(const char *str) const { @@ -162,7 +240,22 @@ int BSAFile::getIndex(const char *str) const void BSAFile::open(const std::string &file) { mFilename = file; - readHeader(); + if(boost::filesystem::exists(file)) + readHeader(); + else + { + { boost::filesystem::fstream(mFilename, std::ios::binary | std::ios::out); } + writeHeader(); + } +} + +/// Close the archive, write the updated headers to the file +void Bsa::BSAFile::close() +{ + if (!mHasChanged) + return; + + writeHeader(); } Files::IStreamPtr BSAFile::getFile(const char *file) @@ -181,3 +274,56 @@ Files::IStreamPtr BSAFile::getFile(const FileStruct *file) { return Files::openConstrainedFileStream (mFilename.c_str (), file->offset, file->fileSize); } + +void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file) +{ + namespace bfs = boost::filesystem; + + auto newStartOfDataBuffer = 12 + (12 + 8) * (mFiles.size() + 1) + mStringBuf.size() + filename.size() + 1; + if (mFiles.empty()) + bfs::resize_file(mFilename, newStartOfDataBuffer); + + bfs::fstream stream(mFilename, std::ios::binary | std::ios::in | std::ios::out); + + FileStruct newFile; + file.seekg(0, std::ios::end); + newFile.fileSize = file.tellg(); + newFile.setNameInfos(mStringBuf.size(), &mStringBuf); + newFile.hash = getHash(filename); + + if(mFiles.empty()) + newFile.offset = newStartOfDataBuffer; + else + { + std::vector buffer; + while (mFiles.front().offset < newStartOfDataBuffer) { + FileStruct& firstFile = mFiles.front(); + buffer.resize(firstFile.fileSize); + + stream.seekg(firstFile.offset, std::ios::beg); + stream.read(buffer.data(), firstFile.fileSize); + + stream.seekp(0, std::ios::end); + firstFile.offset = stream.tellp(); + + stream.write(buffer.data(), firstFile.fileSize); + + //ensure sort order is preserved + std::rotate(mFiles.begin(), mFiles.begin() + 1, mFiles.end()); + } + stream.seekp(0, std::ios::end); + newFile.offset = stream.tellp(); + } + + mStringBuf.insert(mStringBuf.end(), filename.begin(), filename.end()); + mStringBuf.push_back('\0'); + mFiles.push_back(newFile); + + mHasChanged = true; + + mLookup[filename.c_str()] = mFiles.size() - 1; + + stream.seekp(0, std::ios::end); + file.seekg(0, std::ios::beg); + stream << file.rdbuf(); +} diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 3e7538401..fa6e5fc1c 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -43,20 +43,42 @@ namespace Bsa class BSAFile { public: + + #pragma pack(push) + #pragma pack(1) + struct Hash + { + uint32_t low, high; + }; + #pragma pack(pop) + /// Represents one file entry in the archive struct FileStruct { + void setNameInfos(size_t index, + std::vector* stringBuf + ) { + namesOffset = index; + namesBuffer = stringBuf; + } + // File size and offset in file. We store the offset from the // beginning of the file, not the offset into the data buffer // (which is what is stored in the archive.) uint32_t fileSize, offset; + Hash hash; // Zero-terminated file name - const char *name; + const char* name() const { return &(*namesBuffer)[namesOffset]; }; + + uint32_t namesOffset = 0; + std::vector* namesBuffer = nullptr; }; typedef std::vector FileList; protected: + bool mHasChanged = false; + /// Table of files in this archive FileList mFiles; @@ -72,7 +94,7 @@ protected: /// Case insensitive string comparison struct iltstr { - bool operator()(const char *s1, const char *s2) const + bool operator()(const std::string& s1, const std::string& s2) const { return Misc::StringUtils::ciLess(s1, s2); } }; @@ -80,7 +102,7 @@ protected: the files[] vector above. The iltstr ensures that file name checks are case insensitive. */ - typedef std::map Lookup; + typedef std::map Lookup; Lookup mLookup; /// Error handling @@ -88,9 +110,7 @@ protected: /// Read header information from the input source virtual void readHeader(); - - /// Read header information from the input source - + virtual void writeHeader(); /// Get the index of a given file name, or -1 if not found /// @note Thread safe. @@ -106,11 +126,16 @@ public: : mIsLoaded(false) { } - virtual ~BSAFile() = default; + virtual ~BSAFile() + { + close(); + } /// Open an archive file. void open(const std::string &file); + void close(); + /* ----------------------------------- * Archive file routines * ----------------------------------- @@ -131,6 +156,8 @@ public: */ virtual Files::IStreamPtr getFile(const FileStruct* file); + virtual void addFile(const std::string& filename, std::istream& file); + /// Get a list of all files /// @note Thread safe. const FileList &getList() const diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index 77e477ac5..aaeb5bffa 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -226,7 +226,6 @@ void CompressedBSAFile::readHeader() FileStruct fileStruct{}; fileStruct.fileSize = file.getSizeWithoutCompressionFlag(); fileStruct.offset = file.offset; - fileStruct.name = nullptr; mFiles.push_back(fileStruct); fullPaths.push_back(folder); @@ -249,7 +248,7 @@ void CompressedBSAFile::readHeader() } //The vector guarantees that its elements occupy contiguous memory - mFiles[fileIndex].name = reinterpret_cast(mStringBuf.data() + mStringBuffOffset); + mFiles[fileIndex].setNameInfos(mStringBuffOffset, &mStringBuf); fullPaths.at(fileIndex) += "\\" + std::string(mStringBuf.data() + mStringBuffOffset); @@ -276,7 +275,7 @@ void CompressedBSAFile::readHeader() fullPaths.at(fileIndex).c_str() + stringLength + 1u, mStringBuf.data() + mStringBuffOffset); - mFiles[fileIndex].name = reinterpret_cast(mStringBuf.data() + mStringBuffOffset); + mFiles[fileIndex].setNameInfos(mStringBuffOffset, &mStringBuf); mLookup[reinterpret_cast(mStringBuf.data() + mStringBuffOffset)] = fileIndex; mStringBuffOffset += stringLength + 1u; @@ -320,13 +319,19 @@ CompressedBSAFile::FileRecord CompressedBSAFile::getFileRecord(const std::string Files::IStreamPtr CompressedBSAFile::getFile(const FileStruct* file) { - FileRecord fileRec = getFileRecord(file->name); + FileRecord fileRec = getFileRecord(file->name()); if (!fileRec.isValid()) { - fail("File not found: " + std::string(file->name)); + fail("File not found: " + std::string(file->name())); } return getFile(fileRec); } +void CompressedBSAFile::addFile(const std::string& filename, std::istream& file) +{ + assert(false); //not implemented yet + fail("Add file is not implemented for compressed BSA: " + filename); +} + Files::IStreamPtr CompressedBSAFile::getFile(const char* file) { FileRecord fileRec = getFileRecord(file); @@ -430,10 +435,10 @@ void CompressedBSAFile::convertCompressedSizesToUncompressed() { for (auto & mFile : mFiles) { - const FileRecord& fileRecord = getFileRecord(mFile.name); + const FileRecord& fileRecord = getFileRecord(mFile.name()); if (!fileRecord.isValid()) { - fail("Could not find file " + std::string(mFile.name) + " in BSA"); + fail("Could not find file " + std::string(mFile.name()) + " in BSA"); } if (!fileRecord.isCompressed(mCompressedByDefault)) diff --git a/components/bsa/compressedbsafile.hpp b/components/bsa/compressedbsafile.hpp index deddfae38..215a1fc49 100644 --- a/components/bsa/compressedbsafile.hpp +++ b/components/bsa/compressedbsafile.hpp @@ -94,7 +94,7 @@ namespace Bsa Files::IStreamPtr getFile(const char* filePath) override; Files::IStreamPtr getFile(const FileStruct* fileStruct) override; - + void addFile(const std::string& filename, std::istream& file) override; }; } diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index e6d779aab..90899ac61 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -32,7 +32,7 @@ void BsaArchive::listResources(std::map &out, char (*normal { for (std::vector::iterator it = mResources.begin(); it != mResources.end(); ++it) { - std::string ent = it->mInfo->name; + std::string ent = it->mInfo->name(); std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); out[ent] = &*it; @@ -43,7 +43,7 @@ bool BsaArchive::contains(const std::string& file, char (*normalize_function)(ch { for (const auto& it : mResources) { - std::string ent = it.mInfo->name; + std::string ent = it.mInfo->name(); std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); if(file == ent) return true; From 2cb4b62b837353addec6bd48b220b9ccd1d4e232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Thu, 8 Apr 2021 16:32:38 +0000 Subject: [PATCH 58/96] De-hardcode references to mesh files used by the sky. --- apps/openmw/mwrender/sky.cpp | 37 +++++++++++++++++++----------------- files/settings-default.cfg | 24 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 5a6ec06e5..2f2882368 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -45,6 +45,9 @@ #include #include +#include +#include + #include #include "../mwbase/environment.hpp" @@ -1164,7 +1167,7 @@ void SkyManager::create() { assert(!mCreated); - mAtmosphereDay = mSceneManager->getInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot); + mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot); ModVertexAlphaVisitor modAtmosphere(0); mAtmosphereDay->accept(modAtmosphere); @@ -1176,10 +1179,10 @@ void SkyManager::create() mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); osg::ref_ptr atmosphereNight; - if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif")) - atmosphereNight = mSceneManager->getInstance("meshes/sky_night_02.nif", mAtmosphereNightNode); + if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) + atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode); else - atmosphereNight = mSceneManager->getInstance("meshes/sky_night_01.nif", mAtmosphereNightNode); + atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode); atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); ModVertexAlphaVisitor modStars(2); atmosphereNight->accept(modStars); @@ -1193,14 +1196,14 @@ void SkyManager::create() mCloudNode = new osg::PositionAttitudeTransform; mEarlyRenderBinRoot->addChild(mCloudNode); - mCloudMesh = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode); + mCloudMesh = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode); ModVertexAlphaVisitor modClouds(1); mCloudMesh->accept(modClouds); mCloudUpdater = new CloudUpdater; mCloudUpdater->setOpacity(1.f); mCloudMesh->addUpdateCallback(mCloudUpdater); - mCloudMesh2 = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode); + mCloudMesh2 = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode); mCloudMesh2->accept(modClouds); mCloudUpdater2 = new CloudUpdater; mCloudUpdater2->setOpacity(0.f); @@ -1597,7 +1600,7 @@ void SkyManager::update(float duration) if (mParticleNode) { // Morrowind deliberately rotates the blizzard mesh, so so should we. - if (mCurrentParticleEffect == "meshes\\blizzard.nif") + if (mCurrentParticleEffect == Settings::Manager::getString("weatherblizzard", "Models")) quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection); mParticleNode->setAttitude(quat); } @@ -1897,16 +1900,16 @@ void SkyManager::setWaterHeight(float height) void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) { - models.emplace_back("meshes/sky_atmosphere.nif"); - if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif")) - models.emplace_back("meshes/sky_night_02.nif"); - models.emplace_back("meshes/sky_night_01.nif"); - models.emplace_back("meshes/sky_clouds_01.nif"); - - models.emplace_back("meshes\\ashcloud.nif"); - models.emplace_back("meshes\\blightcloud.nif"); - models.emplace_back("meshes\\snow.nif"); - models.emplace_back("meshes\\blizzard.nif"); + models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models")); + if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) + models.emplace_back(Settings::Manager::getString("skynight02", "Models")); + models.emplace_back(Settings::Manager::getString("skynight01", "Models")); + models.emplace_back(Settings::Manager::getString("skyclouds", "Models")); + + models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models")); + models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models")); + models.emplace_back(Settings::Manager::getString("weathersnow", "Models")); + models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models")); textures.emplace_back("textures/tx_mooncircle_full_s.dds"); textures.emplace_back("textures/tx_mooncircle_full_m.dds"); diff --git a/files/settings-default.cfg b/files/settings-default.cfg index d439c4635..78487b173 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1005,6 +1005,30 @@ xbaseanimfemalekf = meshes/xbase_anim_female.kf # File to load xargonianswimkna animations from xargonianswimknakf = meshes/xargonian_swimkna.kf +# Sky atmosphere mesh +skyatmosphere = meshes/sky_atmosphere.nif + +# Sky clouds mesh +skyclouds = meshes/sky_clouds_01.nif + +# Sky stars mesh 01 +skynight01 = meshes/sky_night_01.nif + +# Sky stars mesh 02 +skynight02 = meshes/sky_night_02.nif + +# Ash clouds weather effect +weatherashcloud = meshes/ashcloud.nif + +# Blight clouds weather effect +weatherblightcloud = meshes/blightcloud.nif + +# Snow falling weather effect +weathersnow = meshes/snow.nif + +# Blizzard weather effect +weatherblizzard = meshes/blizzard.nif + [Groundcover] # enable separate groundcover handling From 7f577f5f08306058a434aa1fc90a54b0260b5391 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 8 Apr 2021 19:38:42 +0200 Subject: [PATCH 59/96] Do not compare hash in tests Different std libraries have different implementation that produce different results for the same values. --- apps/openmw_test_suite/esm/variant.cpp | 32 ++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/apps/openmw_test_suite/esm/variant.cpp b/apps/openmw_test_suite/esm/variant.cpp index c00f73d73..9615222fe 100644 --- a/apps/openmw_test_suite/esm/variant.cpp +++ b/apps/openmw_test_suite/esm/variant.cpp @@ -420,7 +420,6 @@ namespace Variant mVariant; Variant::Format mFormat; std::size_t mDataSize {}; - std::size_t mDataHash {}; }; std::string write(const Variant& variant, const Variant::Format format) @@ -442,11 +441,10 @@ namespace return result; } - Variant writeAndRead(const Variant& variant, const Variant::Format format, std::size_t dataSize, std::size_t dataHash) + Variant writeAndRead(const Variant& variant, const Variant::Format format, std::size_t dataSize) { const std::string data = write(variant, format); EXPECT_EQ(data.size(), dataSize); - EXPECT_EQ(std::hash{}(data), dataHash); return read(format, data); } @@ -455,20 +453,20 @@ namespace TEST_P(ESMVariantToESMTest, deserialized_is_equal_to_serialized) { const auto param = GetParam(); - const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize, param.mDataHash); + const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize); ASSERT_EQ(param.mVariant, result); } INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMTest, Values( - WriteToESMTestCase {Variant(), Variant::Format_Gmst, 324, 10398667754238537314ul}, - WriteToESMTestCase {Variant(int{42}), Variant::Format_Global, 345, 2440845426097842853ul}, - WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Global, 345, 8428720798053904009ul}, - WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Info, 336, 11930997575130354755ul}, - WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Local, 336, 11930997575130354755ul}, - WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Global, 345, 7812065815960720679ul}, - WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Local, 334, 5017869102981712080ul}, - WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Info, 336, 12560431547347287906ul}, - WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Local, 336, 12560431547347287906ul} + WriteToESMTestCase {Variant(), Variant::Format_Gmst, 324}, + WriteToESMTestCase {Variant(int{42}), Variant::Format_Global, 345}, + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Global, 345}, + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Info, 336}, + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Local, 336}, + WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Global, 345}, + WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Local, 334}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Info, 336}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Local, 336} )); struct ESMVariantToESMNoneTest : TestWithParam {}; @@ -476,14 +474,14 @@ namespace TEST_P(ESMVariantToESMNoneTest, deserialized_is_none) { const auto param = GetParam(); - const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize, param.mDataHash); + const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize); ASSERT_EQ(Variant(), result); } INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMNoneTest, Values( - WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Gmst, 336, 11930997575130354755ul}, - WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Gmst, 335, 7604528240659685057ul}, - WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Gmst, 336, 12560431547347287906ul} + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Gmst, 336}, + WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Gmst, 335}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Gmst, 336} )); struct ESMVariantWriteToESMFailTest : TestWithParam {}; From 8e1c92d9afa951e1dd4195e81a6161266fbef6e0 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 4 Apr 2021 19:17:10 +0200 Subject: [PATCH 60/96] Use std::variant for ESM::Variant implementation --- apps/openmw_test_suite/esm/variant.cpp | 18 +-- components/esm/variant.cpp | 177 ++++++----------------- components/esm/variant.hpp | 38 ++--- components/esm/variantimp.cpp | 188 +++---------------------- components/esm/variantimp.hpp | 181 ++++-------------------- 5 files changed, 127 insertions(+), 475 deletions(-) diff --git a/apps/openmw_test_suite/esm/variant.cpp b/apps/openmw_test_suite/esm/variant.cpp index 9615222fe..10d35e486 100644 --- a/apps/openmw_test_suite/esm/variant.cpp +++ b/apps/openmw_test_suite/esm/variant.cpp @@ -180,19 +180,19 @@ namespace TEST(ESMVariantGetStringTest, for_default_constructed_should_throw_exception) { - ASSERT_THROW(Variant().getString(), std::runtime_error); + ASSERT_THROW(Variant().getString(), std::bad_variant_access); } TEST(ESMVariantGetStringTest, for_constructed_from_int_should_throw_exception) { const Variant variant(int{42}); - ASSERT_THROW(variant.getString(), std::runtime_error); + ASSERT_THROW(variant.getString(), std::bad_variant_access); } TEST(ESMVariantGetStringTest, for_constructed_from_float_should_throw_exception) { const Variant variant(float{2.7}); - ASSERT_THROW(variant.getString(), std::runtime_error); + ASSERT_THROW(variant.getString(), std::bad_variant_access); } TEST(ESMVariantGetStringTest, for_constructed_from_string_should_return_same_value) @@ -372,40 +372,40 @@ namespace TEST(ESMVariantSetStringTest, for_default_constructed_should_throw_exception) { Variant variant; - ASSERT_THROW(variant.setString("foo"), std::runtime_error); + ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_unknown_should_throw_exception) { Variant variant; variant.setType(VT_Unknown); - ASSERT_THROW(variant.setString("foo"), std::runtime_error); + ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_default_int_should_throw_exception) { Variant variant(int{13}); - ASSERT_THROW(variant.setString("foo"), std::runtime_error); + ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_int_should_throw_exception) { Variant variant; variant.setType(VT_Int); - ASSERT_THROW(variant.setString("foo"), std::runtime_error); + ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_short_should_throw_exception) { Variant variant; variant.setType(VT_Short); - ASSERT_THROW(variant.setString("foo"), std::runtime_error); + ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_float_should_throw_exception) { Variant variant(float{2.7f}); - ASSERT_THROW(variant.setString("foo"), std::runtime_error); + ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_string_should_change_value) diff --git a/components/esm/variant.cpp b/components/esm/variant.cpp index cbcb6e002..3e0417ef7 100644 --- a/components/esm/variant.cpp +++ b/components/esm/variant.cpp @@ -14,106 +14,53 @@ namespace const uint32_t INTV = ESM::FourCC<'I','N','T','V'>::value; const uint32_t FLTV = ESM::FourCC<'F','L','T','V'>::value; const uint32_t STTV = ESM::FourCC<'S','T','T','V'>::value; -} - -ESM::Variant::Variant() : mType (VT_None), mData (nullptr) {} - -ESM::Variant::Variant(const std::string &value) -{ - mData = nullptr; - mType = VT_None; - setType(VT_String); - setString(value); -} - -ESM::Variant::Variant(int value) -{ - mData = nullptr; - mType = VT_None; - setType(VT_Long); - setInteger(value); -} - -ESM::Variant::Variant(float value) -{ - mData = nullptr; - mType = VT_None; - setType(VT_Float); - setFloat(value); -} - -ESM::Variant::~Variant() -{ - delete mData; -} -ESM::Variant& ESM::Variant::operator= (const Variant& variant) -{ - if (&variant!=this) + template + struct GetValue { - VariantDataBase *newData = variant.mData ? variant.mData->clone() : nullptr; - - delete mData; + T operator()(int value) const { return static_cast(value); } - mType = variant.mType; - mData = newData; - } + T operator()(float value) const { return static_cast(value); } - return *this; -} + template + T operator()(const V&) const + { + if constexpr (orDefault) + return T {}; + else + throw std::runtime_error("cannot convert variant"); + } + }; -ESM::Variant& ESM::Variant::operator= (Variant&& variant) -{ - if (&variant!=this) + template + struct SetValue { - delete mData; - - mType = variant.mType; - mData = variant.mData; - - variant.mData = nullptr; - } + T mValue; - return *this; -} + explicit SetValue(T value) : mValue(value) {} -ESM::Variant::Variant (const Variant& variant) -: mType (variant.mType), mData (variant.mData ? variant.mData->clone() : nullptr) -{} + void operator()(int& value) const { value = static_cast(mValue); } -ESM::Variant::Variant(Variant&& variant) -: mType (variant.mType), mData (variant.mData) -{ - variant.mData = nullptr; -} + void operator()(float& value) const { value = static_cast(mValue); } -ESM::VarType ESM::Variant::getType() const -{ - return mType; + template + void operator()(V&) const { throw std::runtime_error("cannot convert variant"); } + }; } std::string ESM::Variant::getString() const { - if (!mData) - throw std::runtime_error ("can not convert empty variant to string"); - - return mData->getString(); + return std::get(mData); } int ESM::Variant::getInteger() const { - if (!mData) - throw std::runtime_error ("can not convert empty variant to integer"); - - return mData->getInteger(); + return std::visit(GetValue{}, mData); } float ESM::Variant::getFloat() const { - if (!mData) - throw std::runtime_error ("can not convert empty variant to float"); - - return mData->getFloat(); + return std::visit(GetValue{}, mData); } void ESM::Variant::read (ESMReader& esm, Format format) @@ -202,9 +149,7 @@ void ESM::Variant::read (ESMReader& esm, Format format) setType (type); - // data - if (mData) - mData->read (esm, format, mType); + std::visit(ReadESMVariantValue {esm, format, mType}, mData); } void ESM::Variant::write (ESMWriter& esm, Format format) const @@ -227,7 +172,7 @@ void ESM::Variant::write (ESMWriter& esm, Format format) const // nothing to do here for GMST format } else - mData->write (esm, format, mType); + std::visit(WriteESMVariantValue {esm, format, mType}, mData); } void ESM::Variant::write (std::ostream& stream) const @@ -246,27 +191,27 @@ void ESM::Variant::write (std::ostream& stream) const case VT_Short: - stream << "variant short: " << mData->getInteger(); + stream << "variant short: " << std::get(mData); break; case VT_Int: - stream << "variant int: " << mData->getInteger(); + stream << "variant int: " << std::get(mData); break; case VT_Long: - stream << "variant long: " << mData->getInteger(); + stream << "variant long: " << std::get(mData); break; case VT_Float: - stream << "variant float: " << mData->getFloat(); + stream << "variant float: " << std::get(mData); break; case VT_String: - stream << "variant string: \"" << mData->getString() << "\""; + stream << "variant string: \"" << std::get(mData) << "\""; break; } } @@ -275,74 +220,50 @@ void ESM::Variant::setType (VarType type) { if (type!=mType) { - VariantDataBase *newData = nullptr; - switch (type) { case VT_Unknown: case VT_None: - - break; // no data + mData = std::monostate {}; + break; case VT_Short: case VT_Int: case VT_Long: - - newData = new VariantIntegerData (mData); + mData = std::visit(GetValue{}, mData); break; case VT_Float: - - newData = new VariantFloatData (mData); + mData = std::visit(GetValue{}, mData); break; case VT_String: - - newData = new VariantStringData (mData); + mData = std::string {}; break; } - delete mData; - mData = newData; mType = type; } } void ESM::Variant::setString (const std::string& value) { - if (!mData) - throw std::runtime_error ("can not assign string to empty variant"); - - mData->setString (value); + std::get(mData) = value; } -void ESM::Variant::setInteger (int value) +void ESM::Variant::setString (std::string&& value) { - if (!mData) - throw std::runtime_error ("can not assign integer to empty variant"); - - mData->setInteger (value); + std::get(mData) = std::move(value); } -void ESM::Variant::setFloat (float value) +void ESM::Variant::setInteger (int value) { - if (!mData) - throw std::runtime_error ("can not assign float to empty variant"); - - mData->setFloat (value); + std::visit(SetValue(value), mData); } -bool ESM::Variant::isEqual (const Variant& value) const +void ESM::Variant::setFloat (float value) { - if (mType!=value.mType) - return false; - - if (!mData) - return true; - - assert (value.mData); - - return mData->isEqual (*value.mData); + std::visit(SetValue(value), mData); } std::ostream& ESM::operator<< (std::ostream& stream, const Variant& value) @@ -350,13 +271,3 @@ std::ostream& ESM::operator<< (std::ostream& stream, const Variant& value) value.write (stream); return stream; } - -bool ESM::operator== (const Variant& left, const Variant& right) -{ - return left.isEqual (right); -} - -bool ESM::operator!= (const Variant& left, const Variant& right) -{ - return !(left==right); -} diff --git a/components/esm/variant.hpp b/components/esm/variant.hpp index 20b3aa76e..f0a16d4d5 100644 --- a/components/esm/variant.hpp +++ b/components/esm/variant.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include namespace ESM { @@ -20,12 +22,10 @@ namespace ESM VT_String }; - class VariantDataBase; - class Variant { VarType mType; - VariantDataBase *mData; + std::variant mData; public: @@ -37,21 +37,17 @@ namespace ESM Format_Local // local script variables in save game files }; - Variant(); + Variant() : mType (VT_None), mData (std::monostate{}) {} - Variant (const std::string& value); - Variant (int value); - Variant (float value); + explicit Variant(const std::string& value) : mType(VT_String), mData(value) {} - ~Variant(); + explicit Variant(std::string&& value) : mType(VT_String), mData(std::move(value)) {} - Variant& operator= (const Variant& variant); - Variant& operator= (Variant && variant); + explicit Variant(int value) : mType(VT_Long), mData(value) {} - Variant (const Variant& variant); - Variant (Variant&& variant); + explicit Variant(float value) : mType(VT_Float), mData(value) {} - VarType getType() const; + VarType getType() const { return mType; } std::string getString() const; ///< Will throw an exception, if value can not be represented as a string. @@ -75,19 +71,27 @@ namespace ESM void setString (const std::string& value); ///< Will throw an exception, if type is not compatible with string. + void setString (std::string&& value); + ///< Will throw an exception, if type is not compatible with string. + void setInteger (int value); ///< Will throw an exception, if type is not compatible with integer. void setFloat (float value); ///< Will throw an exception, if type is not compatible with float. - bool isEqual (const Variant& value) const; + friend bool operator==(const Variant& left, const Variant& right) + { + return std::tie(left.mType, left.mData) == std::tie(right.mType, right.mData); + } + + friend bool operator!=(const Variant& left, const Variant& right) + { + return !(left == right); + } }; std::ostream& operator<<(std::ostream& stream, const Variant& value); - - bool operator== (const Variant& left, const Variant& right); - bool operator!= (const Variant& left, const Variant& right); } #endif diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp index 2b69923d1..74d1351ec 100644 --- a/components/esm/variantimp.cpp +++ b/components/esm/variantimp.cpp @@ -5,71 +5,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -ESM::VariantDataBase::~VariantDataBase() {} - -std::string ESM::VariantDataBase::getString (bool default_) const -{ - if (default_) - return ""; - - throw std::runtime_error ("can not convert variant to string"); -} - -int ESM::VariantDataBase::getInteger (bool default_) const -{ - if (default_) - return 0; - - throw std::runtime_error ("can not convert variant to integer"); -} - -float ESM::VariantDataBase::getFloat (bool default_) const -{ - if (default_) - return 0; - - throw std::runtime_error ("can not convert variant to float"); -} - -void ESM::VariantDataBase::setString (const std::string& value) -{ - throw std::runtime_error ("conversion of string to variant not possible"); -} - -void ESM::VariantDataBase::setInteger (int value) -{ - throw std::runtime_error ("conversion of integer to variant not possible"); -} - -void ESM::VariantDataBase::setFloat (float value) -{ - throw std::runtime_error ("conversion of float to variant not possible"); -} - - - -ESM::VariantStringData::VariantStringData (const VariantDataBase *data) -{ - if (data) - mValue = data->getString (true); -} - -ESM::VariantDataBase *ESM::VariantStringData::clone() const -{ - return new VariantStringData (*this); -} - -std::string ESM::VariantStringData::getString (bool default_) const -{ - return mValue; -} - -void ESM::VariantStringData::setString (const std::string& value) -{ - mValue = value; -} - -void ESM::VariantStringData::read (ESMReader& esm, Variant::Format format, VarType type) +void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, std::string& out) { if (type!=VT_String) throw std::logic_error ("not a string type"); @@ -84,10 +20,10 @@ void ESM::VariantStringData::read (ESMReader& esm, Variant::Format format, VarTy esm.fail ("local variables of type string not supported"); // GMST - mValue = esm.getHString(); + out = esm.getHString(); } -void ESM::VariantStringData::write (ESMWriter& esm, Variant::Format format, VarType type) const +void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, const std::string& in) { if (type!=VT_String) throw std::logic_error ("not a string type"); @@ -102,48 +38,10 @@ void ESM::VariantStringData::write (ESMWriter& esm, Variant::Format format, VarT throw std::runtime_error ("local variables of type string not supported"); // GMST - esm.writeHNString ("STRV", mValue); -} - -bool ESM::VariantStringData::isEqual (const VariantDataBase& value) const -{ - return dynamic_cast (value).mValue==mValue; -} - - - -ESM::VariantIntegerData::VariantIntegerData (const VariantDataBase *data) : mValue (0) -{ - if (data) - mValue = data->getInteger (true); -} - -ESM::VariantDataBase *ESM::VariantIntegerData::clone() const -{ - return new VariantIntegerData (*this); -} - -int ESM::VariantIntegerData::getInteger (bool default_) const -{ - return mValue; -} - -float ESM::VariantIntegerData::getFloat (bool default_) const -{ - return static_cast(mValue); + esm.writeHNString("STRV", in); } -void ESM::VariantIntegerData::setInteger (int value) -{ - mValue = value; -} - -void ESM::VariantIntegerData::setFloat (float value) -{ - mValue = static_cast (value); -} - -void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarType type) +void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int& out) { if (type!=VT_Short && type!=VT_Long && type!=VT_Int) throw std::logic_error ("not an integer type"); @@ -156,12 +54,12 @@ void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarT if (type==VT_Short) { if (value!=value) - mValue = 0; // nan + out = 0; // nan else - mValue = static_cast (value); + out = static_cast (value); } else if (type==VT_Long) - mValue = static_cast (value); + out = static_cast (value); else esm.fail ("unsupported global variable integer type"); } @@ -176,7 +74,7 @@ void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarT esm.fail (stream.str()); } - esm.getHT (mValue); + esm.getHT(out); } else if (format==Variant::Format_Local) { @@ -184,18 +82,18 @@ void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarT { short value; esm.getHT(value); - mValue = value; + out = value; } else if (type==VT_Int) { - esm.getHT(mValue); + esm.getHT(out); } else esm.fail("unsupported local variable integer type"); } } -void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, VarType type) const +void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int in) { if (type!=VT_Short && type!=VT_Long && type!=VT_Int) throw std::logic_error ("not an integer type"); @@ -204,7 +102,7 @@ void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, Var { if (type==VT_Short || type==VT_Long) { - float value = static_cast(mValue); + float value = static_cast(in); esm.writeHNString ("FNAM", type==VT_Short ? "s" : "l"); esm.writeHNT ("FLTV", value); } @@ -222,72 +120,35 @@ void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, Var throw std::runtime_error (stream.str()); } - esm.writeHNT ("INTV", mValue); + esm.writeHNT("INTV", in); } else if (format==Variant::Format_Local) { if (type==VT_Short) - esm.writeHNT ("STTV", (short)mValue); + esm.writeHNT("STTV", static_cast(in)); else if (type == VT_Int) - esm.writeHNT ("INTV", mValue); + esm.writeHNT("INTV", in); else throw std::runtime_error("unsupported local variable integer type"); } } -bool ESM::VariantIntegerData::isEqual (const VariantDataBase& value) const -{ - return dynamic_cast (value).mValue==mValue; -} - - -ESM::VariantFloatData::VariantFloatData (const VariantDataBase *data) : mValue (0) -{ - if (data) - mValue = data->getFloat (true); -} - -ESM::VariantDataBase *ESM::VariantFloatData::clone() const -{ - return new VariantFloatData (*this); -} - -int ESM::VariantFloatData::getInteger (bool default_) const -{ - return static_cast (mValue); -} - -float ESM::VariantFloatData::getFloat (bool default_) const -{ - return mValue; -} - -void ESM::VariantFloatData::setInteger (int value) -{ - mValue = static_cast(value); -} - -void ESM::VariantFloatData::setFloat (float value) -{ - mValue = value; -} - -void ESM::VariantFloatData::read (ESMReader& esm, Variant::Format format, VarType type) +void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, float& out) { if (type!=VT_Float) throw std::logic_error ("not a float type"); if (format==Variant::Format_Global) { - esm.getHNT (mValue, "FLTV"); + esm.getHNT(out, "FLTV"); } else if (format==Variant::Format_Gmst || format==Variant::Format_Info || format==Variant::Format_Local) { - esm.getHT (mValue); + esm.getHT(out); } } -void ESM::VariantFloatData::write (ESMWriter& esm, Variant::Format format, VarType type) const +void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, float in) { if (type!=VT_Float) throw std::logic_error ("not a float type"); @@ -295,15 +156,10 @@ void ESM::VariantFloatData::write (ESMWriter& esm, Variant::Format format, VarTy if (format==Variant::Format_Global) { esm.writeHNString ("FNAM", "f"); - esm.writeHNT ("FLTV", mValue); + esm.writeHNT("FLTV", in); } else if (format==Variant::Format_Gmst || format==Variant::Format_Info || format==Variant::Format_Local) { - esm.writeHNT ("FLTV", mValue); + esm.writeHNT("FLTV", in); } } - -bool ESM::VariantFloatData::isEqual (const VariantDataBase& value) const -{ - return dynamic_cast (value).mValue==mValue; -} diff --git a/components/esm/variantimp.hpp b/components/esm/variantimp.hpp index c1203ddf8..945872811 100644 --- a/components/esm/variantimp.hpp +++ b/components/esm/variantimp.hpp @@ -2,177 +2,58 @@ #define OPENMW_ESM_VARIANTIMP_H #include +#include #include "variant.hpp" namespace ESM { - class VariantDataBase - { - public: - - virtual ~VariantDataBase(); - - virtual VariantDataBase *clone() const = 0; - - virtual std::string getString (bool default_ = false) const; - ///< Will throw an exception, if value can not be represented as a string. - /// - /// \note Numeric values are not converted to strings. - /// - /// \param default_ Return a default value instead of throwing an exception. - /// - /// Default-implementation: throw an exception. - - virtual int getInteger (bool default_ = false) const; - ///< Will throw an exception, if value can not be represented as an integer (implicit - /// casting of float values is permitted). - /// - /// \param default_ Return a default value instead of throwing an exception. - /// - /// Default-implementation: throw an exception. - - virtual float getFloat (bool default_ = false) const; - ///< Will throw an exception, if value can not be represented as a float value. - /// - /// \param default_ Return a default value instead of throwing an exception. - /// - /// Default-implementation: throw an exception. - - virtual void setString (const std::string& value); - ///< Will throw an exception, if type is not compatible with string. - /// - /// Default-implementation: throw an exception. - - virtual void setInteger (int value); - ///< Will throw an exception, if type is not compatible with integer. - /// - /// Default-implementation: throw an exception. - - virtual void setFloat (float value); - ///< Will throw an exception, if type is not compatible with float. - /// - /// Default-implementation: throw an exception. - - virtual void read (ESMReader& esm, Variant::Format format, VarType type) = 0; - ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail - - virtual void write (ESMWriter& esm, Variant::Format format, VarType type) const = 0; - ///< If \a type is not supported by \a format, an exception is thrown. - - virtual bool isEqual (const VariantDataBase& value) const = 0; - ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown. - - }; - - class VariantStringData : public VariantDataBase - { - std::string mValue; - - public: + void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, std::string& value); - VariantStringData (const VariantDataBase *data = nullptr); - ///< Calling the constructor with an incompatible data type will result in a silent - /// default initialisation. + void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, float& value); - VariantDataBase *clone() const override; + void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int& value); - std::string getString (bool default_ = false) const override; - ///< Will throw an exception, if value can not be represented as a string. - /// - /// \note Numeric values are not converted to strings. - /// - /// \param default_ Return a default value instead of throwing an exception. + void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, const std::string& value); - void setString (const std::string& value) override; - ///< Will throw an exception, if type is not compatible with string. + void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, float value); - void read (ESMReader& esm, Variant::Format format, VarType type) override; - ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail + void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int value); - void write (ESMWriter& esm, Variant::Format format, VarType type) const override; - ///< If \a type is not supported by \a format, an exception is thrown. - - bool isEqual (const VariantDataBase& value) const override; - ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown. - }; - - class VariantIntegerData : public VariantDataBase + struct ReadESMVariantValue { - int mValue; - - public: - - VariantIntegerData (const VariantDataBase *data = nullptr); - ///< Calling the constructor with an incompatible data type will result in a silent - /// default initialisation. - - VariantDataBase *clone() const override; + std::reference_wrapper mReader; + Variant::Format mFormat; + VarType mType; - int getInteger (bool default_ = false) const override; - ///< Will throw an exception, if value can not be represented as an integer (implicit - /// casting of float values is permitted). - /// - /// \param default_ Return a default value instead of throwing an exception. + ReadESMVariantValue(ESMReader& reader, Variant::Format format, VarType type) + : mReader(reader), mFormat(format), mType(type) {} - float getFloat (bool default_ = false) const override; - ///< Will throw an exception, if value can not be represented as a float value. - /// - /// \param default_ Return a default value instead of throwing an exception. + void operator()(std::monostate) const {} - void setInteger (int value) override; - ///< Will throw an exception, if type is not compatible with integer. - - void setFloat (float value) override; - ///< Will throw an exception, if type is not compatible with float. - - void read (ESMReader& esm, Variant::Format format, VarType type) override; - ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail - - void write (ESMWriter& esm, Variant::Format format, VarType type) const override; - ///< If \a type is not supported by \a format, an exception is thrown. - - bool isEqual (const VariantDataBase& value) const override; - ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown. + template + void operator()(T& value) const + { + readESMVariantValue(mReader.get(), mFormat, mType, value); + } }; - class VariantFloatData : public VariantDataBase + struct WriteESMVariantValue { - float mValue; - - public: - - VariantFloatData (const VariantDataBase *data = nullptr); - ///< Calling the constructor with an incompatible data type will result in a silent - /// default initialisation. - - VariantDataBase *clone() const override; - - int getInteger (bool default_ = false) const override; - ///< Will throw an exception, if value can not be represented as an integer (implicit - /// casting of float values is permitted). - /// - /// \param default_ Return a default value instead of throwing an exception. - - float getFloat (bool default_ = false) const override; - ///< Will throw an exception, if value can not be represented as a float value. - /// - /// \param default_ Return a default value instead of throwing an exception. - - void setInteger (int value) override; - ///< Will throw an exception, if type is not compatible with integer. - - void setFloat (float value) override; - ///< Will throw an exception, if type is not compatible with float. + std::reference_wrapper mWriter; + Variant::Format mFormat; + VarType mType; - void read (ESMReader& esm, Variant::Format format, VarType type) override; - ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail + WriteESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type) + : mWriter(writer), mFormat(format), mType(type) {} - void write (ESMWriter& esm, Variant::Format format, VarType type) const override; - ///< If \a type is not supported by \a format, an exception is thrown. + void operator()(std::monostate) const {} - bool isEqual (const VariantDataBase& value) const override; - ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown. + template + void operator()(const T& value) const + { + writeESMVariantValue(mWriter.get(), mFormat, mType, value); + } }; } From 9a6f0691b6fec2ca575b9f1f4629e9d890d358c8 Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Thu, 8 Apr 2021 20:13:50 +0200 Subject: [PATCH 61/96] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8cb55d36..81ee0d9b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -148,6 +148,7 @@ Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Feature #5813: Instanced groundcover support + Feature #5814: Bsatool should be able to create BSA archives, not only to extract it Feature #5910: Fall back to delta time when physics can't keep up Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation From 6f7e8d9f5905cccd550ad7c40195a192c3b9848c Mon Sep 17 00:00:00 2001 From: tess <2687892-TescoShoppah@users.noreply.gitlab.com> Date: Thu, 8 Apr 2021 20:57:50 +0000 Subject: [PATCH 62/96] Implement #3983 - Add page to the wizard with links to buy morrowind --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/wizard/methodselectionpage.cpp | 13 +++- apps/wizard/methodselectionpage.hpp | 3 + files/ui/wizard/methodselectionpage.ui | 77 ++++++++++++++++++++++ files/wizard/icons/tango/48x48/dollar.png | Bin 0 -> 7395 bytes files/wizard/wizard.qrc | 1 + 7 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 files/wizard/icons/tango/48x48/dollar.png diff --git a/AUTHORS.md b/AUTHORS.md index d2de85747..611d6b9f6 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -197,6 +197,7 @@ Programmers Sylvain Thesnieres (Garvek) t6 terrorfisch + Tess (tescoShoppah) thegriglat Thomas Luppi (Digmaster) tlmullis diff --git a/CHANGELOG.md b/CHANGELOG.md index f8cb55d36..25bcc18e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,6 +123,7 @@ Feature #2404: Levelled List can not be placed into a container Feature #2686: Timestamps in openmw.log Feature #3171: OpenMW-CS: Instance drag selection + Feature #3983: Wizard: Add link to buy Morrowind Feature #4894: Consider actors as obstacles for pathfinding Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing Feature #4977: Use the "default icon.tga" when an item's icon is not found diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp index e00344af9..37234468b 100644 --- a/apps/wizard/methodselectionpage.cpp +++ b/apps/wizard/methodselectionpage.cpp @@ -1,6 +1,9 @@ #include "methodselectionpage.hpp" #include "mainwizard.hpp" +#include +#include + Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : QWizardPage(parent) { @@ -11,9 +14,12 @@ Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : #ifndef OPENMW_USE_UNSHIELD retailDiscRadioButton->setEnabled(false); existingLocationRadioButton->setChecked(true); + buyLinkButton->released(); #endif - + registerField(QLatin1String("installation.retailDisc"), retailDiscRadioButton); + + connect(buyLinkButton, SIGNAL(released()), this, SLOT(handleBuyButton())); } int Wizard::MethodSelectionPage::nextId() const @@ -24,3 +30,8 @@ int Wizard::MethodSelectionPage::nextId() const return MainWizard::Page_ExistingInstallation; } } + +void Wizard::MethodSelectionPage::handleBuyButton() +{ + QDesktopServices::openUrl(QUrl("https://openmw.org/faq/#do_i_need_morrowind")); +} diff --git a/apps/wizard/methodselectionpage.hpp b/apps/wizard/methodselectionpage.hpp index c189ea171..57d551d27 100644 --- a/apps/wizard/methodselectionpage.hpp +++ b/apps/wizard/methodselectionpage.hpp @@ -17,6 +17,9 @@ namespace Wizard int nextId() const override; + private slots: + void handleBuyButton(); + private: MainWizard *mWizard; diff --git a/files/ui/wizard/methodselectionpage.ui b/files/ui/wizard/methodselectionpage.ui index 4d4d66bad..c2dd26052 100644 --- a/files/ui/wizard/methodselectionpage.ui +++ b/files/ui/wizard/methodselectionpage.ui @@ -147,6 +147,83 @@ + + + + Qt::Horizontal + + + + + + + + + Don't have a copy? + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> + + + + + + + Buy the game + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + diff --git a/files/wizard/icons/tango/48x48/dollar.png b/files/wizard/icons/tango/48x48/dollar.png new file mode 100644 index 0000000000000000000000000000000000000000..a14ba2505d1d09b17fc3572af93b15ad7de7abd4 GIT binary patch literal 7395 zcmeHKc|4SB`yXWq$;B%?bwBqLN3gY!5K|BX zfj|;?OH+H`AI`s42?M{*D}IjwfmZ#o$I*prPvn7FY$lB!Km~KdSX3}IluiSILc7LX z+%q;7ubKCetrAp|iXEQRdo$jzb-N4vnI=O zF6`{fKJ}>_FO|(WIWd~_ASq{UI_J#II}*4pMKSI>WFJ;*T?(5S9Eel)^0CPcXV|^* zOIl2lACYMA_C6)h=!=~s8eV|a5Q3yz*;0Czs)KHiQn+p_3{SlpVwSO!$~?}G-2Svu z!zCbT`|6%USrrRyL8)*0YbB-vb7n^G@dmH&e-$oXx$n8cyN>J_p3Y{&Y}-Puir+N# zIzeHb=Qr&|-iREnGZ0b)kzkx5c=@bsD_`w#Dtf@u`+k zSkh*kGpp9w9h7oAj!Bi69G?E`*jiXF<_RG3rsI<-{z$kuFB zDGur;emr7GeN(!}rDOJ*>&i202FBt=`~rK76Kvf4YUrBfA&2dfJomh-DIG0%-mr1? zP+W?+GVWec+Gb;VvxfFVz2FR2Ipe@6+2duG-ap;#biLAiJa4x9&2!hY;{J76l_JyW zI(6C8RIy4fq*;FbpryB-;>uONx{vDY6Msv@4L*=-u8@jOCk?GeHsAElX{sE#_fn3- ztE*YR7>m9Ef0KBvz9C2Z-LLSEC#$!VNo)_iCdtsIbhjGL3Uo_KT9=o#uet0s{?g9t z6Z4GZ!Uha?RnW8dZstcEJrdie+5X5;x2?#n%A@}IB*}x`UDy3QpKi79Fa^K&D{f7k zllM}LyjO9L9V}Q@xw(sq#l3E?d6WXr_DKk@j4rx(!z7~}p^{wn*eGrREEVf7p6Fqg?WB$s_ z%vwv8Qv-hCejm@Y3Tg5LMx`~gZr*&OJL3B$miTN9VWgNko^V3Z(Dpod@`~4m*V3u+ zt~zLhFuG?v<5sTrRr|skh+E8>wR01`231y)CAo;P3dj1!`Et)_n?S}~@SdpM)n1|f zE4%u}*!3!rA46+{jpnj;qkZdR&uaFLkDMA@NE-<`zOYdEWI^}newml!JErGDxC!Cb zg!=Sc(`zS`CLQw{ROHX_60WUH*btRsm69nOzO&VX?2N*(j-Ef7BNUW!<*I=8q(weu z93lJmNWa33ng@b($S0quzKPlKc8OQU$@1M9U26TSpG3fmWt~2yXc~FV7)ezqyozj` z0u8GfWnsYEc1;xcMX*v@6R-$U)I9=cg{o-kB{-J=6`KWJzGZ{m{>vt8VZPZ6=6bvV z{3%*LKPgJ0$@VZv<{7q^i1%V$JyzV4Dt7upTttdU{6M0MqD**>KFVvSSg0iWtVr=i zW66e~gY5H(LrG_o9CdXgUYprT`D`8#Ms4iL-WeGsDZ8q|Xh4iDt-mbe3(8mW6fx&S-;h;&uB?;|`2af_!-#gl1anRno~nM@Hhvj8 z%<4HQ6MV(w%oXUaEmzf?-}wnn(Zt?5x*uAzPxrl9Zh=YeU3j_BB}pt)4y)gFT2&Ng zNV;^oH4E9?^S+}iq-eP9SeukX`j}(71FZKddOYun>3*wQ0zQUL4!O10*GX4myB@YLI; zMz!Y~#*^Po`u zXXb>u#*0$=+-FrS(OngF7IT48z0G;yiD%H6^_TBIDa7nK^r75y{7v`*w)fGkIi_iK z)#PWzCu8HJ5%IV0+kP85bv4ofzsotq@kQ;Uy7@C7bUS)tbP&{Dho>5XtWJG>fgTkl zrA3>WQvEZa;tyUL$)knWV^ByCNFa)CVnV>1nEZJP0?s};`%?5R8x1$y_HrnI$ciKs zVJJnm>+<7c)0|GPk~J=GEje_zz^gT16(1Dzi--7{{o9}pbMayW6NAuJcuBvxs)*H(}AR47iRrXqB{89PyV zQ`>~L6>GNvvThn$b_uSTsW7D$F7~T)N8qsL-*~Mj;FR2PSLXHPODxg& z%p-1U5zgZteY`mM%nr1u%_=}I3@gAF@Ov&7Mc`zS0dfIjN7dlIDV zwt+yB33T9C>SAM!BQpcFh!iG?sudc@0*-{|h`|mqy-*xL5J=?`!J&Zx3=S?-AF{-Y1Md0V zFbH@F!u8jOxY!WDCQLRJjM74B!GR5g9)g7I5CiM6DKwnDsrgq5prsG-<#Jg#7>vi` zY4NnRm~0;y0*l4M;7Aw}2?Y>PP8fqr423c{8~GGpI83P=GMmog(wPh}pOZ*p26Ocx z5MUhql^hr)n}q_MUmftn@|hGdNr#gG}X90pwbAfCoW^lXS>r zG!#oipr9xn9U_#70(dBBB%Fpu=xCGC%PH*Gbf6N50n4M}Q&9jaB8sSsM4&KGw6=~m z6ou6#K}lrb4vr*K;W|X%poP>~iiW=qI3ogHAA;0^fA1j#5V&#zY_3Qd1B1ci?|xa`0vH82>k(@6c~7MreNmon2mdOrV=HG!D^riJ>LkgfTNd*QkZ2~wV`HTDopkVG^UiPR;N z$mk#0IZPUtM`Tlte1IGP))^?RrF90YFJ+&`k7%AR703}NTn7h-L%u98SP#bEQ2(5s z9)HWRvB51DK##vA;qd&-a|mX!0_arskHY+!DE|v?nf=$K{7>e~VPC9Gn5;0MXnnbM zJjUO;{|n$325UN*%HS~nF7)M)FS0Cq?f@}==>wi;z~d42?fLjME%=r5U;KPcxBsFC zfcmGCf28kExqiy^j}-Vv;Gfy`Q?7rcz&`^2%&z}8xx~J|VN)5vL5~N#KdT{FV~A)oR$OppwuKTau%^YtE2`wU^#?>%wEtlKQ?V zR>=aPIQ94E`BG8(qwQ4=D&EsiQO50Mj4 zSaIM$ly`5p@Xgp^;$ z;p3j(Q!0{4bsalPHs3DU07@-Vef;a@6U8NXjgs~!I)a-XTRqJ`b<-w&_aBifx^arG zP3V%hr1-$2Ij*N5$GUUQ?cB^P#S1t_kxt~!-4Zgr=y`gh?Z9fo?%OV-`##lPR<=>B z6>puHDxB6_5jX+A$xzA5z0yfctO9lHU%-_%dqzx@b7MSP_L8fR&DX@Ggll2vT>6?Z zVp2kbaZ`GwcZj1}hBDPEKFR`*U8)6owI9#=?KVx<)7)%#Ev=RyqtgSupI|Ek_R3(! zq|%31KJ$!>t@Nm~9gv}WNXI~5YVCDbY-@V^ao^D8M@1)Q1oO5!jzyl^>C&e!9Xi)t zRV(jey|+?PIQI_eh#O~h)SZn5CEo29G@ciURp57rYE4}9(~=pxonkNh;gH3G9DD8| zzB=iJL_>RfynI#d6`o|?+tepY%_{e|daAs4tT?%1g!gJhc|zPxy%IMkSP~!NIW*sX nX6yLN#Ru6s{y+*Wrhf*#?j9b?dCi;y5)6blvo)icons/tango/index.theme icons/tango/48x48/folder.png icons/tango/48x48/system-installer.png + icons/tango/48x48/dollar.png images/intropage-background.png From 3e200007784285e57c64fd5c1a86baf5df732572 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Fri, 9 Apr 2021 16:51:44 +0200 Subject: [PATCH 63/96] Add a coverity scan --- .gitlab-ci.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 713cc2601..850407938 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,6 +30,29 @@ stages: paths: - build/install/ +Coverity: + extends: .Debian + only: + - schedules + before_script: + - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic + - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN + - tar xfz /tmp/cov-analysis-linux64.tgz + script: + - CI/before_script.linux.sh + - cd build + - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build . -- -j $(nproc) + after_script: + - tar cfz cov-int.tar.gz cov-int + - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME \ + --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL \ + --form file=@cov-int.tar.gz --form version="`git describe --tags`" \ + --form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID + variables: + CC: gcc + CXX: g++ + timeout: 2h + Debian_GCC: extends: .Debian cache: From 799cf16f3161fe7bfa39c20d17d9c780010de3da Mon Sep 17 00:00:00 2001 From: Jonas Tobias Hopusch Date: Fri, 9 Apr 2021 18:16:05 +0200 Subject: [PATCH 64/96] Attempt to fix #5942 Closes OpenMW/openmw#5942 This is an attempt to apply the fix suggested by @Capostrophic --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fb6721853..f1e40ef7f 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -681,7 +681,7 @@ namespace MWMechanics // Deviating from Morrowind here: it doesn't increase disposition on marginal wins, // which seems to be a bug (MCP fixes it too). // Original logic: x = 0, y = -iPerMinChange - x = -iPerMinChange; + x = iPerMinChange; y = x; // This goes unused. } else From d661a3103e446a22dd79160ec6813ea88b0a05f7 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 8 Apr 2021 00:05:02 +0200 Subject: [PATCH 65/96] Use clang 9 for linux builds on travis --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e84a9ad71..debb2e3dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ addons: - sourceline: 'ppa:openmw/openmw' packages: [ # Dev - build-essential, cmake, clang-tools, ccache, + build-essential, cmake, clang-tools-9, ccache, # Boost libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, # FFmpeg @@ -37,8 +37,8 @@ matrix: os: linux dist: focal env: - - MATRIX_EVAL="CC=clang && CXX=clang++" - - ANALYZE="scan-build --force-analyze-debug-code --use-cc clang --use-c++ clang++" + - MATRIX_EVAL="CC=clang-9 && CXX=clang++-9" + - ANALYZE="scan-build-9 --force-analyze-debug-code --use-cc clang-9 --use-c++ clang++-9" compiler: clang before_install: From aaf975ea351157450164fca18657002435961be9 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 8 Apr 2021 00:24:15 +0200 Subject: [PATCH 66/96] Send travis notifications only for main repo master --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index debb2e3dd..d019b5526 100644 --- a/.travis.yml +++ b/.travis.yml @@ -66,11 +66,13 @@ deploy: repo: OpenMW/openmw notifications: email: + if: repository_slug = OpenMW/openmw AND branch = master recipients: - corrmage+travis-ci@gmail.com on_success: change on_failure: always irc: + if: repository_slug = OpenMW/openmw AND branch = master channels: - "chat.freenode.net#openmw" on_success: change From 75b4871bab239b9f3c92ff9441ae93b488e49574 Mon Sep 17 00:00:00 2001 From: Simon Meulenbeek Date: Fri, 9 Apr 2021 19:28:08 +0000 Subject: [PATCH 67/96] Add Audio settings to openmw-launcher --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/launcher/CMakeLists.txt | 4 + apps/launcher/advancedpage.cpp | 68 +++++++++ apps/launcher/utils/openalutil.cpp | 55 +++++++ apps/launcher/utils/openalutil.hpp | 7 + .../reference/modding/settings/sound.rst | 10 +- files/ui/advancedpage.ui | 144 ++++++++++++++++++ 8 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 apps/launcher/utils/openalutil.cpp create mode 100644 apps/launcher/utils/openalutil.hpp diff --git a/AUTHORS.md b/AUTHORS.md index d2de85747..41313b8aa 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -184,6 +184,7 @@ Programmers sergoz ShadowRadiance Siimacore + Simon Meulenbeek (simonmb) sir_herrbatka smbas Sophie Kirschner (pineapplemachine) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8cb55d36..69a467b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,7 @@ Feature #5456: Basic collada animation support Feature #5457: Realistic diagonal movement Feature #5486: Fixes trainers to choose their training skills based on their base skill points + Feature #5511: Add in game option to toggle HRTF support in OpenMW Feature #5519: Code Patch tab in launcher Feature #5524: Resume failed script execution after reload Feature #5545: Option to allow stealing from an unconscious NPC during combat diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 329d06a57..301823770 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -13,6 +13,7 @@ set(LAUNCHER utils/profilescombobox.cpp utils/textinputdialog.cpp utils/lineedit.cpp + utils/openalutil.cpp ${CMAKE_SOURCE_DIR}/files/windows/launcher.rc ) @@ -31,6 +32,7 @@ set(LAUNCHER_HEADER utils/profilescombobox.hpp utils/textinputdialog.hpp utils/lineedit.hpp + utils/openalutil.hpp ) # Headers that must be pre-processed @@ -47,6 +49,7 @@ set(LAUNCHER_HEADER_MOC utils/textinputdialog.hpp utils/profilescombobox.hpp utils/lineedit.hpp + utils/openalutil.hpp ) @@ -95,6 +98,7 @@ endif (WIN32) target_link_libraries(openmw-launcher ${SDL2_LIBRARY_ONLY} + ${OPENAL_LIBRARY} components ) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index d82dd1be2..b7bf1a249 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -5,11 +5,14 @@ #include #include #include +#include #include #include #include +#include "utils/openalutil.hpp" + Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) @@ -19,7 +22,17 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, setObjectName ("AdvancedPage"); setupUi(this); + for(const char * name : Launcher::enumerateOpenALDevices()) + { + audioDeviceSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name)); + } + for(const char * name : Launcher::enumerateOpenALDevicesHrtf()) + { + hrtfProfileSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name)); + } + loadSettings(); + mCellNameCompleter.setModel(&mCellNameCompleterModel); startDefaultCharacterAtField->setCompleter(&mCellNameCompleter); } @@ -126,6 +139,34 @@ bool Launcher::AdvancedPage::loadSettings() viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera"))); } + // Audio + { + std::string selectedAudioDevice = mEngineSettings.getString("device", "Sound"); + if (selectedAudioDevice.empty() == false) + { + int audioDeviceIndex = audioDeviceSelectorComboBox->findData(QString::fromStdString(selectedAudioDevice)); + if (audioDeviceIndex != -1) + { + audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex); + } + } + int hrtfEnabledIndex = mEngineSettings.getInt("hrtf enable", "Sound"); + if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1) + { + enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1); + } + std::string selectedHRTFProfile = mEngineSettings.getString("hrtf", "Sound"); + if (selectedHRTFProfile.empty() == false) + { + int hrtfProfileIndex = hrtfProfileSelectorComboBox->findData(QString::fromStdString(selectedHRTFProfile)); + if (hrtfProfileIndex != -1) + { + hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex); + } + } + } + + // Camera { loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); @@ -247,6 +288,33 @@ void Launcher::AdvancedPage::saveSettings() mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance)); } } + + // Audio + { + int audioDeviceIndex = audioDeviceSelectorComboBox->currentIndex(); + if (audioDeviceIndex != 0) + { + mEngineSettings.setString("device", "Sound", audioDeviceSelectorComboBox->currentText().toUtf8().constData()); + } + else + { + mEngineSettings.setString("device", "Sound", ""); + } + int hrtfEnabledIndex = enableHRTFComboBox->currentIndex() - 1; + if (hrtfEnabledIndex != mEngineSettings.getInt("hrtf enable", "Sound")) + { + mEngineSettings.setInt("hrtf enable", "Sound", hrtfEnabledIndex); + } + int selectedHRTFProfileIndex = hrtfProfileSelectorComboBox->currentIndex(); + if (selectedHRTFProfileIndex != 0) + { + mEngineSettings.setString("hrtf", "Sound", hrtfProfileSelectorComboBox->currentText().toUtf8().constData()); + } + else + { + mEngineSettings.setString("hrtf", "Sound", ""); + } + } // Camera { diff --git a/apps/launcher/utils/openalutil.cpp b/apps/launcher/utils/openalutil.cpp new file mode 100644 index 000000000..6f76b7130 --- /dev/null +++ b/apps/launcher/utils/openalutil.cpp @@ -0,0 +1,55 @@ +#include +#include +#include + +#include + +#include "openalutil.hpp" + +#ifndef ALC_ALL_DEVICES_SPECIFIER +#define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#endif + +std::vector Launcher::enumerateOpenALDevices() +{ + std::vector devlist; + const ALCchar *devnames; + + if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) + { + devnames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); + } + else + { + devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); + } + + while(devnames && *devnames) + { + devlist.emplace_back(devnames); + devnames += strlen(devnames)+1; + } + return devlist; +} + +std::vector Launcher::enumerateOpenALDevicesHrtf() +{ + std::vector ret; + + ALCdevice *device = alcOpenDevice(nullptr); + if(device && alcIsExtensionPresent(device, "ALC_SOFT_HRTF")) + { + LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; + void* funcPtr = alcGetProcAddress(device, "alcGetStringiSOFT"); + memcpy(&alcGetStringiSOFT, &funcPtr, sizeof(funcPtr)); + ALCint num_hrtf; + alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); + ret.reserve(num_hrtf); + for(ALCint i = 0;i < num_hrtf && i < 20;++i) + { + const ALCchar *entry = alcGetStringiSOFT(device, ALC_HRTF_SPECIFIER_SOFT, i); + ret.emplace_back(entry); + } + } + return ret; +} \ No newline at end of file diff --git a/apps/launcher/utils/openalutil.hpp b/apps/launcher/utils/openalutil.hpp new file mode 100644 index 000000000..4a84fbae7 --- /dev/null +++ b/apps/launcher/utils/openalutil.hpp @@ -0,0 +1,7 @@ +#include + +namespace Launcher +{ + std::vector enumerateOpenALDevices(); + std::vector enumerateOpenALDevicesHrtf(); +} \ No newline at end of file diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index 895b919fb..4cc665582 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -5,7 +5,7 @@ device ------ :Type: string -:Range: +:Range: :Default: "" This setting determines which audio device to use. A blank or missing setting means to use the default device, @@ -13,7 +13,7 @@ which should usually be sufficient, but if you need to explicitly specify a devi The names of detected devices can be found in the openmw.log file in your configuration directory. -This setting can only be configured by editing the settings configuration file. +This setting can be configured by editing the settings configuration file, or in the Audio tab of the OpenMW Launcher. master volume ------------- @@ -111,13 +111,13 @@ Enabling HRTF may also require an OpenAL Soft version greater than 1.17.0, and possibly some operating system configuration. A value of 0 disables HRTF processing, while a value of 1 explicitly enables HRTF processing. The default value is -1, which should enable the feature automatically for most users when possible. -This setting can only be configured by editing the settings configuration file. +This setting can be configured by editing the settings configuration file, or in the Audio tab of the OpenMW Launcher. hrtf ---- :Type: string -:Range: +:Range: :Default: "" This setting specifies which HRTF profile to use when HRTF is enabled. Blank means use the default. @@ -125,4 +125,4 @@ This setting has no effect if HRTF is not enabled based on the hrtf enable setti 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. +This setting can be configured by editing the settings configuration file, or in the Audio tab of the OpenMW Launcher. diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index a3601ce94..a990e9172 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -467,6 +467,150 @@ This setting makes the fog use the actual eye point distance (or so called Eucli + + + Audio + + + + + + + + Audio Device + + + Select your preferred audio device. + + + + + + + + 0 + 0 + + + + + 283 + 0 + + + + 0 + + + + Default + + + + + + + + + + + + HRTF + + + This setting controls HRTF, which simulates 3D sound on stereo systems. + + + + + + + + 0 + 0 + + + + + 283 + 0 + + + + 0 + + + + Automatic + + + + + Off + + + + + On + + + + + + + + + + + + HRTF Profile + + + Select your preferred HRTF profile. + + + + + + + + 0 + 0 + + + + + 283 + 0 + + + + 0 + + + + Default + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + Camera From 6e1c67a9aea864df8dff68ecb0fa3eb5c73c8ed0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 9 Apr 2021 19:11:26 +0200 Subject: [PATCH 68/96] Account for waterwalking when updating position. Otherwise we might trace down the actor at waterlevel at the wrong coordinate. Triggered by multimark mod with waterwalking effect. --- apps/openmw/mwphysics/mtphysics.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 47 +++++++++++++------------ apps/openmw/mwphysics/physicssystem.hpp | 4 +-- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 4be8b2396..4957ef422 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -317,7 +317,7 @@ namespace MWPhysics // init for (auto& data : actorsData) - data.updatePosition(); + data.updatePosition(mCollisionWorld); mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ac8dd92f8..4087ba7e1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -60,6 +60,22 @@ #include "movementsolver.hpp" #include "mtphysics.hpp" +namespace +{ + bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world) + { + if (!physicActor) + return false; + const float halfZ = physicActor->getHalfExtents().z(); + const osg::Vec3f actorPosition = physicActor->getPosition(); + const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); + const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); + MWPhysics::ActorTracer tracer; + tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world); + return (tracer.mFraction >= 1.0f); + } +} + namespace MWPhysics { PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) @@ -347,16 +363,7 @@ namespace MWPhysics bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) { - const Actor* physicActor = getActor(actor); - if (!physicActor) - return false; - const float halfZ = physicActor->getHalfExtents().z(); - const osg::Vec3f actorPosition = physicActor->getPosition(); - const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); - const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); - ActorTracer tracer; - tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get()); - return (tracer.mFraction >= 1.0f); + return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get()); } osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const @@ -772,16 +779,10 @@ namespace MWPhysics const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects(); bool waterCollision = false; - bool moveToWaterSurface = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { - if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) - waterCollision = true; - else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel)) - { - moveToWaterSurface = true; + if (physicActor->getCollisionMode() || !world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) waterCollision = true; - } } physicActor->setCanWaterWalk(waterCollision); @@ -794,7 +795,7 @@ namespace MWPhysics if (!willSimulate) standingOn = physicActor->getStandingOnPtr(); - actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel); + actorsFrameData.emplace_back(std::move(physicActor), standingOn, waterCollision, movement, slowFall, waterlevel); } mMovementQueue.clear(); return actorsFrameData; @@ -937,9 +938,9 @@ namespace MWPhysics } ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, - bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel) + bool waterCollision, osg::Vec3f movement, float slowFall, float waterlevel) : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), - mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), + mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision), mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() { const MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -953,7 +954,7 @@ namespace MWPhysics mWasOnGround = actor->getOnGround(); } - void ActorFrameData::updatePosition() + void ActorFrameData::updatePosition(btCollisionWorld* world) { mActorRaw->updateWorldPosition(); // If physics runs "fast enough", position are interpolated without simulation @@ -961,10 +962,10 @@ namespace MWPhysics // regardless of simulation speed. mActorRaw->applyOffsetChange(); mPosition = mActorRaw->getPosition(); - if (mMoveToWaterSurface) + if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world)) { mPosition.z() = mWaterlevel; - MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z()); + MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false); } mOldHeight = mPosition.z(); mRefpos = mActorRaw->getPtr().getRefData().getPosition(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 5ccbf1a38..ce10b4246 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -79,7 +79,7 @@ namespace MWPhysics struct ActorFrameData { ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); - void updatePosition(); + void updatePosition(btCollisionWorld* world); std::weak_ptr mActor; Actor* mActorRaw; MWWorld::Ptr mStandingOn; @@ -90,7 +90,7 @@ namespace MWPhysics bool mDidJump; bool mFloatToSurface; bool mNeedLand; - bool mMoveToWaterSurface; + bool mWaterCollision; float mWaterlevel; float mSlowFall; float mOldHeight; From 8874a5be22710c06b26f25c83051dddf373d6f67 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 9 Apr 2021 23:06:36 +0200 Subject: [PATCH 69/96] Change (again) the way SetPos behave. Instead of registering the desired change of position and rely on physics simulation to apply it to the world, immediately change the position in the world without reset the simulation. --- apps/openmw/mwbase/world.hpp | 4 +-- apps/openmw/mwphysics/actor.cpp | 12 +------ .../mwscript/transformationextensions.cpp | 21 +++++++++--- apps/openmw/mwworld/worldimp.cpp | 32 ++++--------------- apps/openmw/mwworld/worldimp.hpp | 7 ++-- 5 files changed, 27 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 3d55ad987..6cee36b40 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -281,13 +281,13 @@ namespace MWBase virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; - virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool moveToActive=false) = 0; + virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) = 0; ///< @return an updated Ptr in case the Ptr's cell changes virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; ///< @return an updated Ptr - virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0; + virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 776212ede..f9adc9bc6 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include #include #include @@ -181,6 +179,7 @@ bool Actor::setPosition(const osg::Vec3f& position) if (mSkipSimulation) return false; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; + updateWorldPosition(); applyOffsetChange(); mPreviousPosition = mPosition; mPosition = position; @@ -197,15 +196,6 @@ void Actor::applyOffsetChange() { if (mPositionOffset.length() == 0) return; - if (mPositionOffset.z() != 0) - { - // Often, offset are set in sequence x, y, z - // We don't want actors to be moved under the ground - // Check terrain height at new coordinate and update z offset if necessary - const auto pos = mWorldPosition + mPositionOffset; - const auto terrainHeight = mPtr.getCell()->isExterior() ? MWBase::Environment::get().getWorld()->getTerrainHeightAt(pos) : -std::numeric_limits::max(); - mPositionOffset.z() = std::max(pos.z(), terrainHeight) - mWorldPosition.z(); - } mWorldPosition += mPositionOffset; mPosition += mPositionOffset; mPreviousPosition += mPositionOffset; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 6b92378c7..ba211503e 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -32,7 +32,7 @@ namespace MWScript std::vector actors; MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); for (auto& actor : actors) - MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff); + MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false); } template @@ -284,6 +284,17 @@ namespace MWScript } else if(axis == "z") { + // We should not place actors under ground + if (ptr.getClass().isActor()) + { + float terrainHeight = -std::numeric_limits::max(); + if (ptr.getCell()->isExterior()) + terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos); + + if (pos < terrainHeight) + pos = terrainHeight; + } + newPos[2] = pos; } else @@ -292,7 +303,7 @@ namespace MWScript } dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true)); } }; @@ -428,7 +439,7 @@ namespace MWScript } else { - ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true); + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true, true); } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); @@ -715,7 +726,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); } }; @@ -751,7 +762,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3b50dd6aa..5b9afa4fe 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -862,19 +862,6 @@ namespace MWWorld if (reference == getPlayerPtr()) throw std::runtime_error("can not disable player object"); - // A common pattern to teleport NPC in scripts is a sequence of SetPos/Disable/Enable - // Disable/Enable create a new physics actor, and so the SetPos call is lost - // Call moveObject so that the newly created physics actor will have up-to-date position - if (reference.getClass().isActor()) - { - auto* physactor = mPhysics->getActor(reference); - if (physactor) - { - physactor->applyOffsetChange(); - const auto position = physactor->getSimulationPosition(); - moveObject(reference, position.x(), position.y(), position.z(), true); - } - } reference.getRefData().disable(); if (reference.getCellRef().getRefNum().hasContentFile()) @@ -1251,7 +1238,7 @@ namespace MWWorld return newPtr; } - MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive) + MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive) { int cellX, cellY; positionToIndex(x, y, cellX, cellY); @@ -1266,21 +1253,14 @@ namespace MWWorld return moveObject(ptr, cell, x, y, z, movePhysics); } - MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive) - { - return moveObjectImp(ptr, x, y, z, true, moveToActive); - } - - MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec) + MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive) { auto* actor = mPhysics->getActor(ptr); if (actor) - { actor->adjustPosition(vec); - return ptr; - } + osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; - return moveObject(ptr, newpos.x(), newpos.y(), newpos.z()); + return moveObject(ptr, newpos.x(), newpos.y(), newpos.z(), false, moveToActive && ptr != getPlayerPtr()); } void World::scaleObject (const Ptr& ptr, float scale) @@ -1546,7 +1526,7 @@ namespace MWWorld auto* physactor = mPhysics->getActor(actor); assert(physactor); const auto position = physactor->getSimulationPosition(); - moveObjectImp(actor, position.x(), position.y(), position.z(), false); + moveObject(actor, position.x(), position.y(), position.z(), false, false); } } @@ -1556,7 +1536,7 @@ namespace MWWorld auto* physactor = mPhysics->getActor(*player); assert(physactor); const auto position = physactor->getSimulationPosition(); - moveObjectImp(*player, position.x(), position.y(), position.z(), false); + moveObject(*player, position.x(), position.y(), position.z(), false, false); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 929e035e9..33b23e065 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -138,9 +138,6 @@ namespace MWWorld void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags); - Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false); - ///< @return an updated Ptr in case the Ptr's cell changes - Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); @@ -376,13 +373,13 @@ namespace MWWorld void undeleteObject (const Ptr& ptr) override; - MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive=false) override; + MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) override; ///< @return an updated Ptr in case the Ptr's cell changes MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; ///< @return an updated Ptr - MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec) override; + MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive) override; ///< @return an updated Ptr void scaleObject (const Ptr& ptr, float scale) override; From 8ff4f731fb6bcba2fa85ef0c742a4d2ad2bd74c1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 9 Apr 2021 23:08:51 +0000 Subject: [PATCH 70/96] Make Coverity happy about A2C Initialise member variable --- components/shader/shadervisitor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 612c9011d..b0013538f 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -50,6 +50,7 @@ namespace Shader , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mConvertAlphaTestToAlphaToCoverage(false) , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) From 93954a961c7511946e4b25b46c597eaeaa18cdc7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 09:30:58 +0400 Subject: [PATCH 71/96] Unlock mutex on return to avoid hang --- extern/osg-ffmpeg-videoplayer/videostate.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 7c4bddb01..a35c845db 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -349,7 +349,10 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) vp->pts = pts; if (vp->set_dimensions(w, h) < 0) + { + this->pictq_mutex.unlock(); return -1; + } sws_scale(this->sws_context, pFrame->data, pFrame->linesize, 0, this->video_ctx->height, vp->rgbaFrame->data, vp->rgbaFrame->linesize); From 41c78a889a54ff0e13efba637f151fdf3c992857 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 09:35:31 +0400 Subject: [PATCH 72/96] Check for decompression error code --- components/bsa/compressedbsafile.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index aaeb5bffa..0f3a26e15 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -381,8 +381,10 @@ Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord) LZ4F_decompressionContext_t context = nullptr; LZ4F_createDecompressionContext(&context, LZ4F_VERSION); LZ4F_decompressOptions_t options = {}; - LZ4F_decompress(context, memoryStreamPtr->getRawData(), &uncompressedSize, buffer.get(), &size, &options); - LZ4F_errorCode_t errorCode = LZ4F_freeDecompressionContext(context); + LZ4F_errorCode_t errorCode = LZ4F_decompress(context, memoryStreamPtr->getRawData(), &uncompressedSize, buffer.get(), &size, &options); + if (LZ4F_isError(errorCode)) + fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode)); + errorCode = LZ4F_freeDecompressionContext(context); if (LZ4F_isError(errorCode)) fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode)); } From b96929f3fc94ad0a0ae54438cd4cf1541e606967 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 09:52:46 +0400 Subject: [PATCH 73/96] Avoid division by zero --- apps/launcher/graphicspage.cpp | 5 ++++- apps/openmw/mwgui/settingswindow.cpp | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 01205043e..c6e74573c 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -20,6 +20,9 @@ QString getAspect(int x, int y) { int gcd = std::gcd (x, y); + if (gcd == 0) + return QString(); + int xaspect = x / gcd; int yaspect = y / gcd; // special case: 8 : 5 is usually referred to as 16:10 @@ -298,9 +301,9 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) return result; } - QString aspect = getAspect(mode.w, mode.h); QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h); + QString aspect = getAspect(mode.w, mode.h); if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) { resolution.append(tr("\t(Wide ") + aspect + ")"); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 538b3db5e..6342433c4 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -61,6 +61,9 @@ namespace std::string getAspect (int x, int y) { int gcd = std::gcd (x, y); + if (gcd == 0) + return std::string(); + int xaspect = x / gcd; int yaspect = y / gcd; // special case: 8 : 5 is usually referred to as 16:10 @@ -249,8 +252,10 @@ namespace MWGui std::sort(resolutions.begin(), resolutions.end(), sortResolutions); for (std::pair& resolution : resolutions) { - std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second) - + " (" + getAspect(resolution.first, resolution.second) + ")"; + std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second); + std::string aspect = getAspect(resolution.first, resolution.second); + if (!aspect.empty()) + str = str + " (" + aspect + ")"; if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) mResolutionList->addItem(str); From 124a33d8a314924c6df5095622ca1a597dfdf889 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 10:58:00 +0400 Subject: [PATCH 74/96] Fix uninitialized variables --- apps/opencs/model/world/refidadapterimp.cpp | 22 ++++++++-- apps/opencs/model/world/refidadapterimp.hpp | 43 +++++++++++++++++--- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/esm/loadtes3.cpp | 1 + components/misc/frameratelimiter.hpp | 1 + components/resource/scenemanager.cpp | 1 + components/sdlutil/sdlinputwrapper.cpp | 2 + components/shader/shadervisitor.cpp | 1 + extern/osg-ffmpeg-videoplayer/videostate.cpp | 5 ++- 9 files changed, 66 insertions(+), 11 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index d944adc23..644092f16 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -56,7 +56,9 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns) -: InventoryColumns (columns) {} +: InventoryColumns (columns) +, mEffects(nullptr) +{} CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns) : InventoryRefIdAdapter (UniversalId::Type_Ingredient, columns), @@ -585,7 +587,13 @@ void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& } CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns) -: InventoryColumns (columns) {} +: InventoryColumns (columns) +, mTime(nullptr) +, mRadius(nullptr) +, mColor(nullptr) +, mSound(nullptr) +, mEmitterType(nullptr) +{} CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns) : InventoryRefIdAdapter (UniversalId::Type_Light, columns), mColumns (columns) @@ -1454,7 +1462,15 @@ int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *co } CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns) -: EnchantableColumns (columns) {} +: EnchantableColumns (columns) +, mType(nullptr) +, mHealth(nullptr) +, mSpeed(nullptr) +, mReach(nullptr) +, mChop{nullptr} +, mSlash{nullptr} +, mThrust{nullptr} +{} CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns) : EnchantableRefIdAdapter (UniversalId::Type_Weapon, columns), mColumns (columns) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 0a29afcad..95d1a09a2 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -178,7 +178,11 @@ namespace CSMWorld const RefIdColumn *mName; const RefIdColumn *mScript; - NameColumns (const ModelColumns& base) : ModelColumns (base) {} + NameColumns (const ModelColumns& base) + : ModelColumns (base) + , mName(nullptr) + , mScript(nullptr) + {} }; /// \brief Adapter for IDs with names (all but levelled lists and statics) @@ -247,7 +251,12 @@ namespace CSMWorld const RefIdColumn *mWeight; const RefIdColumn *mValue; - InventoryColumns (const NameColumns& base) : NameColumns (base) {} + InventoryColumns (const NameColumns& base) + : NameColumns (base) + , mIcon(nullptr) + , mWeight(nullptr) + , mValue(nullptr) + {} }; /// \brief Adapter for IDs that can go into an inventory @@ -405,7 +414,11 @@ namespace CSMWorld const RefIdColumn *mEnchantment; const RefIdColumn *mEnchantmentPoints; - EnchantableColumns (const InventoryColumns& base) : InventoryColumns (base) {} + EnchantableColumns (const InventoryColumns& base) + : InventoryColumns (base) + , mEnchantment(nullptr) + , mEnchantmentPoints(nullptr) + {} }; /// \brief Adapter for enchantable IDs @@ -474,7 +487,11 @@ namespace CSMWorld const RefIdColumn *mQuality; const RefIdColumn *mUses; - ToolColumns (const InventoryColumns& base) : InventoryColumns (base) {} + ToolColumns (const InventoryColumns& base) + : InventoryColumns (base) + , mQuality(nullptr) + , mUses(nullptr) + {} }; /// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes) @@ -549,7 +566,17 @@ namespace CSMWorld const RefIdColumn *mAiPackages; std::map mServices; - ActorColumns (const NameColumns& base) : NameColumns (base) {} + ActorColumns (const NameColumns& base) + : NameColumns (base) + , mHello(nullptr) + , mFlee(nullptr) + , mFight(nullptr) + , mAlarm(nullptr) + , mInventory(nullptr) + , mSpells(nullptr) + , mDestinations(nullptr) + , mAiPackages(nullptr) + {} }; /// \brief Adapter for actor IDs (handles common AI functionality) @@ -2054,7 +2081,11 @@ namespace CSMWorld const RefIdColumn *mLevList; const RefIdColumn *mNestedListLevList; - LevListColumns (const BaseColumns& base) : BaseColumns (base) {} + LevListColumns (const BaseColumns& base) + : BaseColumns (base) + , mLevList(nullptr) + , mNestedListLevList(nullptr) + {} }; template diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 331927155..b6fe05d0f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -815,6 +815,7 @@ namespace MWRender RenderingManager::RayResult result; result.mHit = false; result.mHitRefnum.mContentFile = -1; + result.mHitRefnum.mIndex = -1; result.mRatio = 0; if (intersector->containsIntersections()) { diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index d953f1dc2..84a31b3bd 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -42,6 +42,7 @@ void ESM::Header::load (ESMReader &esm) MasterData m; m.name = esm.getHString(); m.size = esm.getHNLong ("DATA"); + m.index = -1; mMaster.push_back (m); } diff --git a/components/misc/frameratelimiter.hpp b/components/misc/frameratelimiter.hpp index b8e210165..b727074d2 100644 --- a/components/misc/frameratelimiter.hpp +++ b/components/misc/frameratelimiter.hpp @@ -13,6 +13,7 @@ namespace Misc explicit FrameRateLimiter(std::chrono::duration maxFrameDuration, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) : mMaxFrameDuration(std::chrono::duration_cast(maxFrameDuration)) + , mLastFrameDuration(0) , mLastMeasurement(now) {} diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 66d48f971..287365a83 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -226,6 +226,7 @@ namespace Resource , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mConvertAlphaTestToAlphaToCoverage(false) , mInstanceCache(new MultiObjectCache) , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 8d6a124e2..ca223ae3b 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -375,6 +375,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v pack_evt.y = mMouseY = evt.motion.y; pack_evt.xrel = evt.motion.xrel; pack_evt.yrel = evt.motion.yrel; + pack_evt.type = SDL_MOUSEMOTION; if (mFirstMouseMove) { // first event should be treated as non-relative, since there's no point of reference @@ -387,6 +388,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v { mMouseZ += pack_evt.zrel = (evt.wheel.y * 120); pack_evt.z = mMouseZ; + pack_evt.type = SDL_MOUSEWHEEL; } else { diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 612c9011d..b0013538f 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -50,6 +50,7 @@ namespace Shader , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mConvertAlphaTestToAlphaToCoverage(false) , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index a35c845db..c153aa14c 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -50,8 +50,9 @@ VideoState::VideoState() , av_sync_type(AV_SYNC_DEFAULT) , audio_st(nullptr) , video_st(nullptr), frame_last_pts(0.0) - , video_clock(0.0), sws_context(nullptr), pictq_size(0) - , pictq_rindex(0), pictq_windex(0) + , video_clock(0.0), sws_context(nullptr) + , sws_context_w(0), sws_context_h(0) + , pictq_size(0), pictq_rindex(0), pictq_windex(0) , mSeekRequested(false) , mSeekPos(0) , mVideoEnded(false) From c989fac67b6de8e6df9676fbf34a6f06d1b053e6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 11:20:12 +0400 Subject: [PATCH 75/96] Add bound for pointers cache size, as it specified in docs --- apps/openmw/mwworld/cells.cpp | 9 +++++---- apps/openmw/mwworld/cells.hpp | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 15c1b46ba..40ad62a40 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -132,9 +132,11 @@ void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector& reader) : mStore (store), mReader (reader), - mIdCache (Settings::Manager::getInt("pointers cache size", "Cells"), std::pair ("", (CellStore*)nullptr)), mIdCacheIndex (0) -{} +{ + int cacheSize = std::max(Settings::Manager::getInt("pointers cache size", "Cells"), 0); + mIdCache = IdCache(cacheSize, std::pair ("", (CellStore*)nullptr)); +} MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y) { @@ -259,8 +261,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell, MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) { // First check the cache - for (std::vector >::iterator iter (mIdCache.begin()); - iter!=mIdCache.end(); ++iter) + for (IdCache::iterator iter (mIdCache.begin()); iter!=mIdCache.end(); ++iter) if (iter->first==name && iter->second) { Ptr ptr = getPtr (name, *iter->second); diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 90ede409b..654d9a14b 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -28,11 +28,12 @@ namespace MWWorld /// \brief Cell container class Cells { + typedef std::vector > IdCache; const MWWorld::ESMStore& mStore; std::vector& mReader; mutable std::map mInteriors; mutable std::map, CellStore> mExteriors; - std::vector > mIdCache; + IdCache mIdCache; std::size_t mIdCacheIndex; Cells (const Cells&); From 903b89a0ffafed6f92bfc0c6265d37aeedcdc70e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 11:21:53 +0400 Subject: [PATCH 76/96] Add bound for UI scale factor, as it specified in docs --- apps/openmw/mwgui/inventorywindow.cpp | 2 +- apps/openmw/mwinput/controllermanager.cpp | 2 +- apps/openmw/mwinput/mousemanager.cpp | 2 +- apps/openmw/mwrender/localmap.cpp | 2 +- components/fontloader/fontloader.cpp | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index b0749d4bd..6152efaa9 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -71,7 +71,7 @@ namespace MWGui , mUpdateTimer(0.f) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - if (uiScale > 1.0) + if (uiScale > 0.f) mScaleFactor = uiScale; mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 48091541c..d17a4bd95 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -70,7 +70,7 @@ namespace MWInput } float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - if (uiScale != 0.f) + if (uiScale > 0.f) mInvUiScalingFactor = 1.f / uiScale; float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input"); diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 4816470ff..8df116baa 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -37,7 +37,7 @@ namespace MWInput , mGuiCursorEnabled(true) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - if (uiScale != 0.f) + if (uiScale > 0.f) mInvUiScalingFactor = 1.f / uiScale; int w,h; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 64931aa88..25d859e54 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -90,7 +90,7 @@ LocalMap::LocalMap(osg::Group* root) { // Increase map resolution, if use UI scaling float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - if (uiScale > 1.0) + if (uiScale > 0.f) mMapResolution *= uiScale; SceneUtil::FindByNameVisitor find("Scene Root"); diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 2bed079e1..98fce32d2 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -569,7 +569,8 @@ namespace Gui resolution = std::min(960, std::max(48, resolution)); float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - resolution *= uiScale; + if (uiScale > 0.f) + resolution *= uiScale; MyGUI::xml::ElementPtr resolutionNode = resourceNode->createChild("Property"); resolutionNode->addAttribute("key", "Resolution"); From 1db369f4184a083ed6c57be8f3d4341ebc3da1d6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 11:26:54 +0400 Subject: [PATCH 77/96] Do not use unchecked value in calculations --- apps/openmw/mwgui/spellcreationdialog.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index f9de469e2..5a5dec60f 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -393,7 +393,8 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - if (MyGUI::utility::parseInt(mPriceLabel->getCaption()) > playerGold) + int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); + if (price > playerGold) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); return; @@ -401,8 +402,6 @@ namespace MWGui mSpell.mName = mNameEdit->getCaption(); - int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); // add gold to NPC trading gold pool From f984e96b347730469da4d49f22a489bfef316522 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 12:23:03 +0400 Subject: [PATCH 78/96] Use conventional names for atan2 arguments --- apps/openmw/mwmechanics/aiavoiddoor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index 73a638563..6a59ae2bf 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -45,13 +45,13 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont return true; //Door is no longer opening ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door - float x = pos.pos[0] - tPos.pos[0]; - float y = pos.pos[1] - tPos.pos[1]; + float x = pos.pos[1] - tPos.pos[1]; + float y = pos.pos[0] - tPos.pos[0]; actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); // Turn away from the door and move when turn completed - if (zTurn(actor, std::atan2(x,y) + getAdjustedAngle(), osg::DegreesToRadians(5.f))) + if (zTurn(actor, std::atan2(y,x) + getAdjustedAngle(), osg::DegreesToRadians(5.f))) actor.getClass().getMovementSettings(actor).mPosition[1] = 1; else actor.getClass().getMovementSettings(actor).mPosition[1] = 0; From 45b1c68af437ea26b193fd823ffea84f79eb2864 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 12:32:12 +0400 Subject: [PATCH 79/96] Remove annotation which does not work --- components/crashcatcher/crashcatcher.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 4ad856548..b4b2a4a0c 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -146,11 +146,10 @@ static void gdb_info(pid_t pid) /* * Create a temp file to put gdb commands into. * Note: POSIX.1-2008 declares that the file should be already created with mode 0600 by default. - * Modern systems implement it and and suggest to do not touch masks in multithreaded applications. + * Modern systems implement it and suggest to do not touch masks in multithreaded applications. * So CoverityScan warning is valid only for ancient versions of stdlib. */ strcpy(respfile, "/tmp/gdb-respfile-XXXXXX"); - // coverity[secure_temp] if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != nullptr) { fprintf(f, "attach %d\n" From 400cae58e56a17df3ec8d4a35e5615618ba8d064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Sat, 10 Apr 2021 12:52:42 +0000 Subject: [PATCH 80/96] Collada user documentation --- .../reference/modding/custom-models/index.rst | 19 +++++ .../pipeline-blender-collada.rst | 84 +++++++++++++++++++ .../custom-models/pipeline-blender-nif.rst | 17 ++++ .../pipeline-blender-osgnative.rst} | 27 ++---- docs/source/reference/modding/index.rst | 1 + .../modding/texture-modding/index.rst | 1 - 6 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 docs/source/reference/modding/custom-models/index.rst create mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-collada.rst create mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-nif.rst rename docs/source/reference/modding/{texture-modding/native-mesh-format.rst => custom-models/pipeline-blender-osgnative.rst} (77%) diff --git a/docs/source/reference/modding/custom-models/index.rst b/docs/source/reference/modding/custom-models/index.rst new file mode 100644 index 000000000..8f575bf3c --- /dev/null +++ b/docs/source/reference/modding/custom-models/index.rst @@ -0,0 +1,19 @@ +############# +Custom Models +############# + +Custom models can be imported into OpenMW using a variety of formats. Below is a quick overview of supported formats, followed by separate articles with further look at the pipelines. + +* **COLLADA** has no license restrictions and is suitable for modding as well as standalone games based on the OpenMW engine. It supports static and animated models. While it doesn't yet work in all parts of the engine, work is being done to resolve the remaining limitations. + +* **OSG native** has no license restrictions, but currently supports only static, non-animated models. + +* **NIF** is the proprietary format used in the original Morrowind game. It supports static and animated models and everything else the format included in the original game. + +.. toctree:: + :caption: Table of Contents + :maxdepth: 1 + + pipeline-blender-collada + pipeline-blender-osgnative + pipeline-blender-nif diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada.rst b/docs/source/reference/modding/custom-models/pipeline-blender-collada.rst new file mode 100644 index 000000000..48a19cb7c --- /dev/null +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada.rst @@ -0,0 +1,84 @@ +############################## +Blender to OpenMW with Collada +############################## + +First, let's take a look at the pipeline requirements and how the fundamental properties of a scene translate from Blender to OpenMW. + +Requirements +------------ +* `OpenMW 0.47 `_ or later +* `Blender 2.81 `_ or later. Latest confirmed, working version is Blender 2.91 +* `Better COLLADA Exporter `_ tuned for OpenMW +* A model you would like to export + +In addition, OpenMW needs to be configured to read COLLADA (dae) files instead of the default format (nif). In settings.cfg under [Models] section... TODO + +Location +-------- + +Objects keep their visual location and origin they had in the original scene. + + +Rotation +-------- + +* Blender’s +Z axis is up axis in OpenMW +* Blender’s +Y axis is front axis in OpenMW +* Blender’s X axis is left-right axis in OpenMW + + +Scale +----- + +Scale ratio between Blender and OpenMW is 70 to 1. This means 70 blender units translate to 1 m in OpenMW. + +However, a scale factor like this is impractical to work with. A better approach is to work with a scale of 1 Blender unit = 1m and apply the 70 scale factor in the Better COLLADA Exporter. The exporter will automatically scale all object, mesh, armature and animation data. + + +Exporter settings - static models +--------------------------------- + +Better COLLADA Exporter offers various options which are rather straightforward for static models. The important one is last in the list, to apply a scaling factor of 70 to the whole scene, so 1 blender unit equals 1 m in OpenMW. The following settings should be good for general use. +It's also very important to have "export selected" box checked, as otherwise the exporter may just fail with an error message. It's also important to have the correct window open, and the models selected before exporting. + + +Animated models to OpenMW +------------------------- + +Animated models are those where a hierarchy of bones, known as armature, deforms the mesh and makes things move. Besides the topics covered above, the following requirements apply. + +Armature +-------- + +* For animated models, a single armature per COLLADA file is advised to avoid any potential problems. +* There needs to be a single top-most bone in the armature’s hierarchy, where both the deformation and control bones fall under it. +* Not all bones need to be exported. By disabing the bone’s “Deform” property and using the corresponding option in the exporter, it is possible to export only the bones needed for animation. + + +Animations +---------- + +Every action in Blender is exported as its own animation clip in COLLADA. Actions you don't wish to export need to have "-noexp" added to their name, with the corresponding option enabled in the exporter. + +Due to current limitations of the format / exporter, the keyframes of any action must not overlap the keyframes of any other action. Thus in practice, the keyframes for each action need to be manually offset to their unique range on the timeline. + +An animated .dae file needs a corresponding animation definition file, or textkeys, for OpenMW to understand. Textkeys are set in .txt file with the same name as the model. E.g. OpenMWDude.dae -> OpenMWDude.txt , each line having a textkey and a double number for timesignature. E.g. idle: start 0.03333333333333333. + +Root Motion +----------- + +OpenMW can read the movement of the root (top-most) bone and use it to move objects in the game world. For this to work, the root bone must be animated to move through space. The root bone must, in its default pose, be alligned with the world. + + +Exporter Settings +----------------- + +For animated models, use the following exporter settings. Before export, select all objects you wish to include in the exported file. TODO + + + + + + + + diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-nif.rst b/docs/source/reference/modding/custom-models/pipeline-blender-nif.rst new file mode 100644 index 000000000..a1b7b0f70 --- /dev/null +++ b/docs/source/reference/modding/custom-models/pipeline-blender-nif.rst @@ -0,0 +1,17 @@ +########################## +Blender to OpenMW with NIF +########################## + +There is a lot of information available around the Internet on how to work with NIF files. We recommend you refer to https://www.niftools.org/ for more information. + +For Blender specifically, you will need the following requirements. + +Requirements +------------ +* `OpenMW `_ +* `Blender 2.8+ `_ +* Either `Niftools addon `_ +* Or `Morrowind Blender Plugin `_ +* A model you would like to export + + diff --git a/docs/source/reference/modding/texture-modding/native-mesh-format.rst b/docs/source/reference/modding/custom-models/pipeline-blender-osgnative.rst similarity index 77% rename from docs/source/reference/modding/texture-modding/native-mesh-format.rst rename to docs/source/reference/modding/custom-models/pipeline-blender-osgnative.rst index c2ddd26ba..95bc7e5ac 100644 --- a/docs/source/reference/modding/texture-modding/native-mesh-format.rst +++ b/docs/source/reference/modding/custom-models/pipeline-blender-osgnative.rst @@ -1,17 +1,9 @@ -################## -Native Mesh Format -################## +################################# +Blender to OpenMW with OSG native +################################# -This article explains how to export a model from Blender to OpenMW using the OSG model format. -Starting with OpenMW version 0.38 we can utilize the OSG native model format. -The OSG model format doesn't yet support all the features that NIF's support, -but works for basic models. For more details on the format, refer to -`this forum post `_. - -Previously, NIF files were the only way to get models into the game. -Unfortunately, the NIF format is proprietary, bloated, -and the available exporters are not in great shape. -For example, the Blender NIF exporter currently only works with the very old Blender 2.49. +This article explains how to export a model from Blender to OpenMW using the OSG model format. It supports only basic, static models. +For more details on the format, refer to `this forum post `_. Prerequisites ############# @@ -103,12 +95,5 @@ Using shaders/normal maps ######################### See :ref:`OSG Native Files` + -Conclusion -########## - -These are the basics of getting a textured, static model from Blender into the game. -In the future, we will want a way to add texture animations, -skeletal animations, separate collision shapes, -and some other features that are currently only available via NIF files. -We will likely add these features to the native OSG format after OpenMW 1.0. \ No newline at end of file diff --git a/docs/source/reference/modding/index.rst b/docs/source/reference/modding/index.rst index 69ec0a56a..df98137d8 100644 --- a/docs/source/reference/modding/index.rst +++ b/docs/source/reference/modding/index.rst @@ -22,6 +22,7 @@ about creating new content for OpenMW, please refer to mod-install settings/index texture-modding/index + custom-models/index font extended paths diff --git a/docs/source/reference/modding/texture-modding/index.rst b/docs/source/reference/modding/texture-modding/index.rst index 3e0b359ee..aa2802af5 100644 --- a/docs/source/reference/modding/texture-modding/index.rst +++ b/docs/source/reference/modding/texture-modding/index.rst @@ -13,4 +13,3 @@ to texture modding in OpenMW. texture-basics convert-bump-mapped-mods - native-mesh-format From 010f290fd525f4b71eacb63a9d2a590b9f34dc11 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 11 Apr 2021 14:07:12 +0200 Subject: [PATCH 81/96] Update OSX deployment target to 10.14 To support std::variant --- CI/before_script.osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 36fa79299..f9191eb89 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -16,7 +16,7 @@ cmake \ -D CMAKE_CXX_FLAGS="-stdlib=libc++" \ -D CMAKE_C_FLAGS_RELEASE="-g -O0" \ -D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \ --D CMAKE_OSX_DEPLOYMENT_TARGET="10.12" \ +-D CMAKE_OSX_DEPLOYMENT_TARGET="10.14" \ -D CMAKE_BUILD_TYPE=RELEASE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \ -D BUILD_OPENMW=TRUE \ From b91be1e8035f36fdfd5cee44b706bafd82a2a047 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 11 Apr 2021 14:12:31 +0200 Subject: [PATCH 82/96] Catch exceptions in ResolutionListener --- apps/openmw/mwworld/containerstore.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 635485dde..86c5ec331 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -77,10 +77,24 @@ MWWorld::ResolutionListener::~ResolutionListener() { if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty()) { - for(const auto&& ptr : mStore) - ptr.getRefData().setCount(0); + try + { + for(const auto&& ptr : mStore) + ptr.getRefData().setCount(0); + } + catch(const std::exception& e) + { + Log(Debug::Warning) << "Failed to clear temporary container contents of " << mStore.mPtr.get()->mBase->mId << ": " << e.what(); + } mStore.fillNonRandom(mStore.mPtr.get()->mBase->mInventory, "", mStore.mSeed); - addScripts(mStore, mStore.mPtr.mCell); + try + { + addScripts(mStore, mStore.mPtr.mCell); + } + catch(const std::exception& e) + { + Log(Debug::Warning) << "Failed to restart item scripts inside " << mStore.mPtr.get()->mBase->mId << ": " << e.what(); + } mStore.mResolved = false; } } From fda639eb57f3180a4a87b8d2879ffbb381f7eb32 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 14 Mar 2021 16:21:33 +0100 Subject: [PATCH 83/96] Remove unused forward declarations --- apps/openmw/engine.hpp | 20 -------------------- apps/openmw/mwbase/windowmanager.hpp | 1 - apps/openmw/mwgui/container.hpp | 6 ------ apps/openmw/mwgui/dialogue.hpp | 5 ----- apps/openmw/mwgui/race.hpp | 5 ----- apps/openmw/mwgui/review.hpp | 5 ----- apps/openmw/mwgui/settingswindow.hpp | 5 ----- apps/openmw/mwgui/spellbuyingwindow.hpp | 5 ----- apps/openmw/mwgui/statswindow.hpp | 2 -- apps/openmw/mwgui/textinput.hpp | 5 ----- apps/openmw/mwgui/travelwindow.hpp | 6 ------ apps/openmw/mwgui/windowbase.hpp | 6 ------ apps/openmw/mwgui/windowmanagerimp.hpp | 1 - apps/openmw/mwgui/windowpinnablebase.hpp | 2 -- apps/openmw/mwscript/interpretercontext.hpp | 10 ---------- apps/openmw/mwworld/scene.hpp | 1 - apps/openmw/mwworld/worldimp.hpp | 2 -- components/nifosg/controller.hpp | 5 ----- 18 files changed, 92 deletions(-) diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index ff362f4b6..1aef62df5 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -33,26 +33,6 @@ namespace Compiler class Context; } -namespace MWScript -{ - class ScriptManager; -} - -namespace MWSound -{ - class SoundManager; -} - -namespace MWWorld -{ - class World; -} - -namespace MWGui -{ - class WindowManager; -} - namespace Files { struct ConfigurationManager; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 29d404777..9bbea9c51 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -32,7 +32,6 @@ namespace MyGUI namespace ESM { - struct Class; class ESMReader; class ESMWriter; struct CellId; diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index feda123fb..85c0dddc6 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -6,11 +6,6 @@ #include "itemmodel.hpp" -namespace MWWorld -{ - class Environment; -} - namespace MyGUI { class Gui; @@ -19,7 +14,6 @@ namespace MyGUI namespace MWGui { - class WindowManager; class ContainerWindow; class ItemView; class SortFilterItemModel; diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 02401f2e1..ac6303e20 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -15,11 +15,6 @@ namespace Gui class MWList; } -namespace MWGui -{ - class WindowManager; -} - namespace MWGui { class ResponseCallback; diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index 0299c2a1a..170c1dbce 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -7,11 +7,6 @@ #include -namespace MWGui -{ - class WindowManager; -} - namespace MWRender { class RaceSelectionPreview; diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index bd17c7afb..cb847536d 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -11,11 +11,6 @@ namespace ESM struct Spell; } -namespace MWGui -{ - class WindowManager; -} - namespace MWGui { class ReviewDialog : public WindowModal diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 6f25dd114..c268514dc 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -3,11 +3,6 @@ #include "windowbase.hpp" -namespace MWGui -{ - class WindowManager; -} - namespace MWGui { class SettingsWindow : public WindowBase diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp index 622548c95..f46c43796 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.hpp +++ b/apps/openmw/mwgui/spellbuyingwindow.hpp @@ -15,11 +15,6 @@ namespace MyGUI class Widget; } -namespace MWGui -{ - class WindowManager; -} - namespace MWGui { class SpellBuyingWindow : public ReferenceInterface, public WindowBase diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index 24f302580..bf78cde34 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -6,8 +6,6 @@ namespace MWGui { - class WindowManager; - class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener { public: diff --git a/apps/openmw/mwgui/textinput.hpp b/apps/openmw/mwgui/textinput.hpp index 84d9d032d..4d365eb44 100644 --- a/apps/openmw/mwgui/textinput.hpp +++ b/apps/openmw/mwgui/textinput.hpp @@ -3,11 +3,6 @@ #include "windowbase.hpp" -namespace MWGui -{ - class WindowManager; -} - namespace MWGui { class TextInputDialog : public WindowModal diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index 962d17161..00b7db730 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -11,12 +11,6 @@ namespace MyGUI class Widget; } -namespace MWGui -{ - class WindowManager; -} - - namespace MWGui { class TravelWindow : public ReferenceInterface, public WindowBase diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 8afb2321e..90ef2118d 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -3,11 +3,6 @@ #include "layout.hpp" -namespace MWBase -{ - class WindowManager; -} - namespace MWWorld { class Ptr; @@ -15,7 +10,6 @@ namespace MWWorld namespace MWGui { - class WindowManager; class DragAndDrop; class WindowBase: public Layout diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index cc1a1b694..3fd8b132f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -114,7 +114,6 @@ namespace MWGui class TrainingWindow; class SpellIcons; class MerchantRepair; - class Repair; class SoulgemDialog; class Recharge; class CompanionWindow; diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index a94212819..c91f0a148 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -5,8 +5,6 @@ namespace MWGui { - class WindowManager; - class WindowPinnableBase: public WindowBase { public: diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index c1481d6d0..298454bcd 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -10,16 +10,6 @@ #include "../mwworld/ptr.hpp" -namespace MWSound -{ - class SoundManager; -} - -namespace MWInput -{ - struct MWInputManager; -} - namespace MWScript { class Locals; diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index a70d3ccdd..f87a0ca73 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -36,7 +36,6 @@ namespace Loading namespace DetourNavigator { struct Navigator; - class Water; } namespace MWRender diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 33b23e065..5447e20c3 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -60,8 +60,6 @@ namespace ToUTF8 class Utf8Encoder; } -struct ContentLoader; - namespace MWPhysics { class Object; diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 96beafcbb..b45916693 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -24,11 +24,6 @@ namespace osg class Material; } -namespace osgParticle -{ - class Emitter; -} - namespace NifOsg { From efb241f1de280987a79526c1d817c1f9ebe5feaa Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 29 Mar 2021 19:44:10 +0200 Subject: [PATCH 84/96] Use override instead of virtual --- apps/openmw/mwsound/soundmanagerimp.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 6b9de800f..934402cd4 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -141,7 +141,7 @@ namespace MWSound public: SoundManager(const VFS::Manager* vfs, bool useSound); - virtual ~SoundManager(); + ~SoundManager() override; void processChangedSettings(const Settings::CategorySettingVector& settings) override; From 32981bcd88d411090d324e410e0084297b8c3f97 Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 29 Mar 2021 19:44:23 +0200 Subject: [PATCH 85/96] Constify a few things --- apps/openmw/mwgui/timeadvancer.cpp | 4 ++-- apps/openmw/mwgui/timeadvancer.hpp | 4 ++-- apps/openmw/mwrender/objectpaging.hpp | 2 +- apps/openmw/mwstate/quicksavemanager.cpp | 4 ++-- apps/openmw/mwstate/quicksavemanager.hpp | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwgui/timeadvancer.cpp b/apps/openmw/mwgui/timeadvancer.cpp index a07da1682..c38094ae4 100644 --- a/apps/openmw/mwgui/timeadvancer.cpp +++ b/apps/openmw/mwgui/timeadvancer.cpp @@ -58,12 +58,12 @@ namespace MWGui } } - int TimeAdvancer::getHours() + int TimeAdvancer::getHours() const { return mHours; } - bool TimeAdvancer::isRunning() + bool TimeAdvancer::isRunning() const { return mRunning; } diff --git a/apps/openmw/mwgui/timeadvancer.hpp b/apps/openmw/mwgui/timeadvancer.hpp index 8367b5a8b..b8456f376 100644 --- a/apps/openmw/mwgui/timeadvancer.hpp +++ b/apps/openmw/mwgui/timeadvancer.hpp @@ -14,8 +14,8 @@ namespace MWGui void stop(); void onFrame(float dt); - int getHours(); - bool isRunning(); + int getHours() const; + bool isRunning() const; // signals typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index 65f53d530..c24cdf4f8 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -63,7 +63,7 @@ namespace MWRender { std::set mDisabled; std::set mBlacklist; - bool operator==(const RefTracker&other) { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; } + bool operator==(const RefTracker&other) const { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; } }; RefTracker mRefTracker; RefTracker mRefTrackerNew; diff --git a/apps/openmw/mwstate/quicksavemanager.cpp b/apps/openmw/mwstate/quicksavemanager.cpp index df078e026..bf1781520 100644 --- a/apps/openmw/mwstate/quicksavemanager.cpp +++ b/apps/openmw/mwstate/quicksavemanager.cpp @@ -18,14 +18,14 @@ void MWState::QuickSaveManager::visitSave(const Slot *saveSlot) } } -bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) +bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) const { if(mOldestSlotVisited == nullptr) return true; return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp); } -bool MWState::QuickSaveManager::shouldCreateNewSlot() +bool MWState::QuickSaveManager::shouldCreateNewSlot() const { return (mSlotsVisited < mMaxSaves); } diff --git a/apps/openmw/mwstate/quicksavemanager.hpp b/apps/openmw/mwstate/quicksavemanager.hpp index a5237d7c3..cdeff42c2 100644 --- a/apps/openmw/mwstate/quicksavemanager.hpp +++ b/apps/openmw/mwstate/quicksavemanager.hpp @@ -13,8 +13,8 @@ namespace MWState{ unsigned int mSlotsVisited; const Slot *mOldestSlotVisited; private: - bool shouldCreateNewSlot(); - bool isOldestSave(const Slot *compare); + bool shouldCreateNewSlot() const; + bool isOldestSave(const Slot *compare) const; public: QuickSaveManager(std::string &saveName, unsigned int maxSaves); ///< A utility class to manage multiple quicksave slots From 95042a2a688e674fae259df8a23616d97780cdee Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 11 Apr 2021 20:31:24 +0200 Subject: [PATCH 86/96] Use the number of logical cores on the CI on OSX --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 713cc2601..c26235d0a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -105,7 +105,7 @@ MacOS: - rm -fr build/* # remove anything in the build directory - CI/before_install.osx.sh - CI/before_script.osx.sh - - cd build; make -j2 package + - cd build; make -j $(sysctl -n hw.logicalcpu) package - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done artifacts: paths: From 634556be9de94456d4019aeb331df97d4184bb83 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 11 Apr 2021 17:57:36 +0200 Subject: [PATCH 87/96] Add setting to allow following creatures to find path over water surface --- apps/launcher/advancedpage.cpp | 1 + apps/openmw/mwmechanics/aipackage.cpp | 7 ++++++- docs/source/reference/modding/settings/game.rst | 16 ++++++++++++++++ files/settings-default.cfg | 4 ++++ files/ui/advancedpage.ui | 10 ++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index b7bf1a249..683d44119 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -108,6 +108,7 @@ bool Launcher::AdvancedPage::loadSettings() int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics"); if (numPhysicsThreads >= 0) physicsThreadsSpinBox->setValue(numPhysicsThreads); + loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game"); } // Visuals diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 214aad320..87ee562c6 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -411,10 +411,15 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const { + static const bool allowToFollowOverWaterSurface = Settings::Manager::getBool("allow actors to follow over water surface", "Game"); + const MWWorld::Class& actorClass = actor.getClass(); DetourNavigator::Flags result = DetourNavigator::Flag_none; - if (actorClass.isPureWaterCreature(actor) || (getTypeId() != AiPackageTypeId::Wander && actorClass.canSwim(actor))) + if (actorClass.isPureWaterCreature(actor) + || (getTypeId() != AiPackageTypeId::Wander + && ((allowToFollowOverWaterSurface && getTypeId() == AiPackageTypeId::Follow) + || actorClass.canSwim(actor)))) result |= DetourNavigator::Flag_swim; if (actorClass.canWalk(actor)) diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 4e1fe1318..878485b3b 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -439,3 +439,19 @@ graphic herbalism Some mods add harvestable container models. When this setting is enabled, activating a container using a harvestable model will visually harvest from it instead of opening the menu. When this setting is turned off or when activating a regular container, the menu will open as usual. + +allow actors to follow over water surface +--------------------- + +:Type: boolean +:Range: True/False +:Default: True + +If enabled actors will always find path over the water surface when following other actors. This makes OpenMW behaviour closer to the vanilla engine. + +If disabled actors without the ability to swim will not follow other actors to the water. + +.. note:: + Has effect only when Navigator is enabled. + +This setting can be controlled in Advanced tab of the launcher. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 78487b173..68c27abe5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -364,6 +364,10 @@ always allow stealing from knocked out actors = false # Enables visually harvesting plants for models that support it. graphic herbalism = true +# Give actors an ability to swim over water surface when they follow other actor independently from their ability to swim +# (true, false) +allow actors to follow over water surface = true + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index a990e9172..594372aab 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -153,6 +153,16 @@ + + + + Give NPC an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. + + + Always allow NPC to follow over water surface + + + From 28fc21792e7e4078029139b8f06828354ffde78d Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 11 Apr 2021 19:17:23 +0200 Subject: [PATCH 88/96] Allow water walking actors to find path over water surface --- apps/openmw/mwmechanics/actorutil.cpp | 6 ++++++ apps/openmw/mwmechanics/actorutil.hpp | 1 + apps/openmw/mwmechanics/aipackage.cpp | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp index e27c9de49..04cbb8e9f 100644 --- a/apps/openmw/mwmechanics/actorutil.cpp +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -23,4 +23,10 @@ namespace MWMechanics MWBase::World* world = MWBase::Environment::get().getWorld(); return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); } + + bool hasWaterWalking(const MWWorld::Ptr& actor) + { + const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); + return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; + } } diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index 1e993f560..a226fc9cb 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -31,6 +31,7 @@ namespace MWMechanics MWWorld::Ptr getPlayer(); bool isPlayerInCombat(); bool canActorMoveByZAxis(const MWWorld::Ptr& actor); + bool hasWaterWalking(const MWWorld::Ptr& actor); template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 87ee562c6..8dcf37355 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -419,7 +419,8 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: if (actorClass.isPureWaterCreature(actor) || (getTypeId() != AiPackageTypeId::Wander && ((allowToFollowOverWaterSurface && getTypeId() == AiPackageTypeId::Follow) - || actorClass.canSwim(actor)))) + || actorClass.canSwim(actor) + || hasWaterWalking(actor)))) result |= DetourNavigator::Flag_swim; if (actorClass.canWalk(actor)) From 56ede535b5e2f8bf818c4ade2c9456fb399190cf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 12 Apr 2021 08:31:45 +0200 Subject: [PATCH 89/96] Don't perform a hit test outside the page's bounds --- CHANGELOG.md | 1 + apps/openmw/mwgui/bookpage.cpp | 76 +++++++++++++++------------------- 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c7834566..a506079e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,7 @@ Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Bug #5912: ImprovedBound mod doesn't work Bug #5914: BM: The Swimmer can't reach destination + Bug #5923: Clicking on empty spaces between journal entries might show random topics Bug #5934: AddItem command doesn't accept negative values Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index c70783f39..fba136f88 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -1,5 +1,7 @@ #include "bookpage.hpp" +#include + #include "MyGUI_RenderItem.h" #include "MyGUI_RenderManager.h" #include "MyGUI_TextureUtility.h" @@ -894,6 +896,27 @@ protected: return mIsPageReset || (mPage != page); } + std::optional getAdjustedPos(int left, int top, bool move = false) + { + if (!mBook) + return {}; + + if (mPage >= mBook->mPages.size()) + return {}; + + MyGUI::IntPoint pos (left, top); +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) + // work around inconsistency in MyGUI where the mouse press coordinates aren't + // transformed by the current Layer (even though mouse *move* events are). + if(!move) + pos = mNode->getLayer()->getPosition(left, top); +#endif + pos.left -= mCroppedParent->getAbsoluteLeft (); + pos.top -= mCroppedParent->getAbsoluteTop (); + pos.top += mViewTop; + return pos; + } + public: typedef TypesetBookImpl::StyleImpl Style; @@ -952,16 +975,10 @@ public: void onMouseMove (int left, int top) { - if (!mBook) - return; - - if (mPage >= mBook->mPages.size()) - return; - - left -= mCroppedParent->getAbsoluteLeft (); - top -= mCroppedParent->getAbsoluteTop (); - - Style * hit = mBook->hitTestWithMargin (left, mViewTop + top); + Style * hit = nullptr; + if(auto pos = getAdjustedPos(left, top, true)) + if(pos->top <= mViewBottom) + hit = mBook->hitTestWithMargin (pos->left, pos->top); if (mLastDown == MyGUI::MouseButton::None) { @@ -991,24 +1008,11 @@ public: void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) { - if (!mBook) - return; + auto pos = getAdjustedPos(left, top); - if (mPage >= mBook->mPages.size()) - return; - - // work around inconsistency in MyGUI where the mouse press coordinates aren't - // transformed by the current Layer (even though mouse *move* events are). - MyGUI::IntPoint pos (left, top); -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - pos = mNode->getLayer()->getPosition(left, top); -#endif - pos.left -= mCroppedParent->getAbsoluteLeft (); - pos.top -= mCroppedParent->getAbsoluteTop (); - - if (mLastDown == MyGUI::MouseButton::None) + if (pos && mLastDown == MyGUI::MouseButton::None) { - mFocusItem = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); + mFocusItem = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr; mItemActive = true; dirtyFocusItem (); @@ -1019,25 +1023,11 @@ public: void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) { - if (!mBook) - return; - - if (mPage >= mBook->mPages.size()) - return; - - // work around inconsistency in MyGUI where the mouse release coordinates aren't - // transformed by the current Layer (even though mouse *move* events are). - MyGUI::IntPoint pos (left, top); -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - pos = mNode->getLayer()->getPosition(left, top); -#endif - - pos.left -= mCroppedParent->getAbsoluteLeft (); - pos.top -= mCroppedParent->getAbsoluteTop (); + auto pos = getAdjustedPos(left, top); - if (mLastDown == id) + if (pos && mLastDown == id) { - Style * item = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); + Style * item = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr; bool clicked = mFocusItem == item; From 9a87940fc6319fc855af5c6f4281425e370b5af8 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 12 Apr 2021 11:12:15 +0200 Subject: [PATCH 90/96] Use `rule` instead of `only` only/except will likely be deprecated: https://docs.gitlab.com/ee/ci/yaml/README.html#onlyexcept-basic --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2faeb820a..da712a2dc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,8 +32,8 @@ stages: Coverity: extends: .Debian - only: - - schedules + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' before_script: - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN From dc10ab7bad235184872c9b74564ca63f23d66a4f Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 12 Apr 2021 11:26:33 +0200 Subject: [PATCH 91/96] Install curl in the coverity job --- .gitlab-ci.yml | 2 +- CI/install_debian_deps.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2faeb820a..6ec4a327e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,7 +35,7 @@ Coverity: only: - schedules before_script: - - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic + - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN - tar xfz /tmp/cov-analysis-linux64.tgz script: diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 3e7ab7fca..490fad0da 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -27,6 +27,8 @@ declare -rA GROUPED_DEPS=( # These dependencies can alternatively be built and linked statically. [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" + + [coverity]="curl" # Pre-requisites for building MyGUI and OSG for static linking. # From 1e955fb2e7df23fc193ef5b0a146e2965b9bc215 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 12 Apr 2021 14:01:22 +0200 Subject: [PATCH 92/96] Fix the path for the coverity build --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2faeb820a..c7b889833 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,8 +40,7 @@ Coverity: - tar xfz /tmp/cov-analysis-linux64.tgz script: - CI/before_script.linux.sh - - cd build - - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build . -- -j $(nproc) + - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) after_script: - tar cfz cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME \ From 539b46aaf0e3dfb34b86ff7933d40d0035b12dea Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 12 Apr 2021 16:01:09 +0200 Subject: [PATCH 93/96] Use different cache keys for different macOS builds --- .gitlab-ci.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 715a88874..f8cd18106 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -115,7 +115,7 @@ Debian_Clang_tests: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 -MacOS: +.MacOS: image: macos-11-xcode-12 tags: - macos @@ -123,8 +123,14 @@ MacOS: only: variables: - $CI_PROJECT_ID == "7107382" + cache: + paths: + - ccache/ script: - rm -fr build/* # remove anything in the build directory + - export CCACHE_BASEDIR="$(pwd)" + - export CCACHE_DIR="$(pwd)/ccache" + - mkdir -pv "${CCACHE_DIR}" - CI/before_install.osx.sh - CI/before_script.osx.sh - cd build; make -j $(sysctl -n hw.logicalcpu) package @@ -134,9 +140,17 @@ MacOS: - build/OpenMW-*.dmg - "build/**/*.log" +macOS11_Xcode12: + extends: .MacOS + image: macos-11-xcode-12 + cache: + key: macOS11_Xcode12.v1 + macOS10.15_Xcode11: - extends: MacOS + extends: .MacOS image: macos-10.15-xcode-11 + cache: + key: macOS10.15_Xcode11.v1 variables: &engine-targets targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" From 663ad5e19207c83ee75dc151a80a83ed5695fcbe Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 12 Apr 2021 16:02:57 +0200 Subject: [PATCH 94/96] Print ccache stats for macOS builds --- .gitlab-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f8cd18106..10d443dd1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -131,10 +131,12 @@ Debian_Clang_tests: - export CCACHE_BASEDIR="$(pwd)" - export CCACHE_DIR="$(pwd)/ccache" - mkdir -pv "${CCACHE_DIR}" + - ccache -z -M "${CCACHE_SIZE}" - CI/before_install.osx.sh - CI/before_script.osx.sh - cd build; make -j $(sysctl -n hw.logicalcpu) package - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done + - ccache -s artifacts: paths: - build/OpenMW-*.dmg @@ -145,12 +147,16 @@ macOS11_Xcode12: image: macos-11-xcode-12 cache: key: macOS11_Xcode12.v1 + variables: + CCACHE_SIZE: 3G macOS10.15_Xcode11: extends: .MacOS image: macos-10.15-xcode-11 cache: key: macOS10.15_Xcode11.v1 + variables: + CCACHE_SIZE: 3G variables: &engine-targets targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" From c10273675effb8e7c00ba5bfcd1d4e4f7a096a6f Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 12 Apr 2021 17:13:38 +0200 Subject: [PATCH 95/96] Massively increase coverity's timeout --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 10d443dd1..6ccf02d1e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -50,7 +50,7 @@ Coverity: variables: CC: gcc CXX: g++ - timeout: 2h + timeout: 8h Debian_GCC: extends: .Debian From ccb62ad8b0fdddd2b108df5666f35465bc877adc Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 12 Apr 2021 20:23:23 +0200 Subject: [PATCH 96/96] Only build openmw in coverity --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ccf02d1e..a04b6a1cd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,7 +40,8 @@ Coverity: - tar xfz /tmp/cov-analysis-linux64.tgz script: - CI/before_script.linux.sh - - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) + # Add more than just `openmw` once we can build everything under 3h + - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw after_script: - tar cfz cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME \