diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fedc707bd7..cdda455dac 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -108,8 +108,8 @@ Coverity: - ccache/ variables: CCACHE_SIZE: 2G - CC: clang-12 - CXX: clang++-12 + CC: clang + CXX: clang++ CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -O0 before_script: @@ -125,18 +125,34 @@ Coverity: - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.linux.sh - cov-analysis-linux64-*/bin/cov-configure --template --comptype prefix --compiler ccache - # Remove the specific targets and build everything once we can do it under 3h - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) - ccache -svv - 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="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA" - --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" + - echo "OPENMW_JOB_ID=$CI_JOB_ID" >> build.env artifacts: + expire_in: 1 day paths: - /builds/OpenMW/openmw/cov-int/build-log.txt + - /builds/OpenMW/openmw/cov-int.tar.gz + reports: + dotenv: build.env + +Coverity_Upload: + image: ubuntu:24.04 + stage: build + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + before_script: + - CI/install_debian_deps.sh coverity_upload + script: + - echo "$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/$OPENMW_JOB_ID/artifacts/cov-int.tar.gz" + - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME + --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL + --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA" + --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" + --form url="$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/$OPENMW_JOB_ID/artifacts/cov-int.tar.gz" + needs: + - Coverity Ubuntu_GCC: extends: .Ubuntu diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 4f0e7cdb69..aada722fb1 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -11,7 +11,8 @@ print_help() { declare -rA GROUPED_DEPS=( [gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config mold" [clang]="binutils clang make cmake ccache curl unzip git pkg-config mold" - [coverity]="binutils clang-12 make cmake ccache curl unzip git pkg-config" + [coverity]="binutils clang make cmake ccache curl unzip git pkg-config file" + [coverity_upload]="curl" [gcc_preprocess]=" binutils build-essential diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d8e3cade9..57ebeefcfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 85) +set(OPENMW_LUA_API_REVISION 87) set(OPENMW_POSTPROCESSING_API_REVISION 3) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/components_tests/lua/testscriptscontainer.cpp b/apps/components_tests/lua/testscriptscontainer.cpp index 4f3cca1b87..9c4c656b32 100644 --- a/apps/components_tests/lua/testscriptscontainer.cpp +++ b/apps/components_tests/lua/testscriptscontainer.cpp @@ -638,8 +638,9 @@ CUSTOM: customdata.lua sol::object deserialized = LuaUtil::deserialize(lua.sol(), data2.mScripts[0].mData, &serializer1); EXPECT_TRUE(deserialized.is()); sol::table table = deserialized; - for (const auto& [key, value] : table) + if (!table.empty()) { + const auto [key, value] = *table.cbegin(); EXPECT_TRUE(key.is()); EXPECT_TRUE(value.is()); EXPECT_EQ(key.as(), (ESM::RefNum{ 42, 34 })); diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 0473676f93..7cdc2bcd98 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -215,8 +215,6 @@ int main(int argc, char** argv) std::cerr << "ERROR: " << e.what() << std::endl; return 1; } - - return 0; } namespace diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 2a63e1a962..39396f1273 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -291,6 +291,7 @@ bool Launcher::SettingsPage::loadSettings() } } loadSettingBool(Settings::sound().mCameraListener, *cameraListenerCheckBox); + dopplerSpinBox->setValue(Settings::sound().mDopplerFactor); } // Interface Changes @@ -485,6 +486,8 @@ void Launcher::SettingsPage::saveSettings() const bool cCameraListener = cameraListenerCheckBox->checkState() != Qt::Unchecked; Settings::sound().mCameraListener.set(cCameraListener); + + Settings::sound().mDopplerFactor.set(dopplerSpinBox->value()); } // Interface Changes diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 4ce2108ec9..a591c196c9 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1224,6 +1224,51 @@ + + + + + + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> + + + Doppler Factor + + + + + + + + 0 + 0 + + + + + 283 + 0 + + + + 2 + + + 0.000000000000000 + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.250000000000000 + + + + + diff --git a/apps/opencs/model/world/idcollection.cpp b/apps/opencs/model/world/idcollection.cpp index ca56eceb69..7890dc47b8 100644 --- a/apps/opencs/model/world/idcollection.cpp +++ b/apps/opencs/model/world/idcollection.cpp @@ -59,14 +59,13 @@ namespace CSMWorld const Record* IdCollection::searchRecord(std::uint16_t index, int plugin) const { - auto found = mIndices.find({ plugin, index }); - if (found != mIndices.end()) - { - int index = searchId(found->second); - if (index != -1) - return &getRecord(index); - } - return nullptr; + const auto it = mIndices.find({ plugin, index }); + if (it == mIndices.end()) + return nullptr; + const int recordIndex = searchId(it->second); + if (recordIndex == -1) + return nullptr; + return &getRecord(recordIndex); } const std::string* IdCollection::getLandTexture(std::uint16_t index, int plugin) const diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 0ea8451774..2a66878a04 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -5,7 +5,8 @@ #include #include -#include +#include +#include #include #include @@ -28,7 +29,6 @@ #include -#include #include #include diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 61574de3ac..5772c555a3 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -75,6 +75,7 @@ namespace MWBase const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) = 0; + virtual void jailTimeServed(const MWWorld::Ptr& actor, int days) = 0; virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0; virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; virtual void onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon, diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 532bc771ba..5591b7205c 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -234,6 +234,8 @@ namespace MWBase const osg::Vec3f& pos, const osg::Vec3f& dir, const osg::Vec3f& up, bool underwater) = 0; + virtual void setListenerVel(const osg::Vec3f& vel) = 0; + virtual void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) = 0; void setSimulationTimeScale(float scale) { mSimulationTimeScale = scale; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 157c12af23..74bd9f6a5c 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -496,6 +496,8 @@ namespace MWBase virtual float getSunVisibility() const = 0; virtual float getSunPercentage() const = 0; + virtual float getPhysicsFrameRateDt() const = 0; + virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 2fbaa8d8ac..9244e9dd2f 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -4,6 +4,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -86,46 +87,6 @@ namespace MWGui // We should not worsen corprus when in prison player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24); - - const auto& skillStore = MWBase::Environment::get().getESMStore()->get(); - std::set skills; - for (int day = 0; day < mDays; ++day) - { - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - const ESM::Skill* skill = skillStore.searchRandom({}, prng); - skills.insert(skill); - - MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill->mId); - if (skill->mId == ESM::Skill::Security || skill->mId == ESM::Skill::Sneak) - value.setBase(std::min(100.f, value.getBase() + 1)); - else - value.setBase(std::max(0.f, value.getBase() - 1)); - } - - const MWWorld::Store& gmst - = MWBase::Environment::get().getESMStore()->get(); - - std::string message; - if (mDays == 1) - message = gmst.find("sNotifyMessage42")->mValue.getString(); - else - message = gmst.find("sNotifyMessage43")->mValue.getString(); - - message = Misc::StringUtils::format(message, mDays); - - for (const ESM::Skill* skill : skills) - { - int skillValue = player.getClass().getNpcStats(player).getSkill(skill->mId).getBase(); - std::string skillMsg = gmst.find("sNotifyMessage44")->mValue.getString(); - if (skill->mId == ESM::Skill::Sneak || skill->mId == ESM::Skill::Security) - skillMsg = gmst.find("sNotifyMessage39")->mValue.getString(); - - skillMsg = Misc::StringUtils::format(skillMsg, skill->mName, skillValue); - message += "\n" + skillMsg; - } - - std::vector buttons; - buttons.emplace_back("#{Interface:OK}"); - MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); + MWBase::Environment::get().getLuaManager()->jailTimeServed(player, mDays); } } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index c883718214..5543caf09f 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -775,12 +775,10 @@ namespace MWGui , mGlobalMapRender(std::make_unique(localMapRender->getRoot(), workQueue)) , mEditNoteDialog() { - static bool registered = false; - if (!registered) - { + [[maybe_unused]] static const bool registered = [] { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - registered = true; - } + return true; + }(); mEditNoteDialog.setVisible(false); mEditNoteDialog.eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditOk); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 558c25fb9a..5684e9ef2d 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -587,6 +587,7 @@ namespace MWGui getWidget(mAvailableEffectsList, "AvailableEffects"); getWidget(mUsedEffectsView, "UsedEffects"); getWidget(mPriceLabel, "PriceLabel"); + getWidget(mPlayerGold, "PlayerGold"); getWidget(mBuyButton, "BuyButton"); getWidget(mCancelButton, "CancelButton"); @@ -613,6 +614,10 @@ namespace MWGui mPtr = actor; mNameEdit->setCaption({}); + MWWorld::Ptr player = MWMechanics::getPlayer(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + mPlayerGold->setCaptionWithReplacing(MyGUI::utility::toString(playerGold)); + startEditing(); } diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index f4624c89b3..f2c440d305 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -190,6 +190,7 @@ namespace MWGui MyGUI::Button* mBuyButton; MyGUI::Button* mCancelButton; MyGUI::TextBox* mPriceLabel; + MyGUI::TextBox* mPlayerGold; ESM::Spell mSpell; }; diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index f5830b2cee..0bb8dbf9ba 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -29,7 +29,7 @@ namespace MWGui : WindowBase("openmw_trainingwindow.layout") { getWidget(mTrainingOptions, "TrainingOptions"); - getWidget(mCancelButton, "CancelButton"); + getWidget(mCancelButton, "OkButton"); getWidget(mPlayerGold, "PlayerGold"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onCancelButtonClicked); @@ -123,14 +123,14 @@ namespace MWGui MyGUI::Button* button = mTrainingOptions->createWidget(price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip - MyGUI::IntCoord(5, 5 + i * lineHeight, mTrainingOptions->getWidth() - 10, lineHeight), + MyGUI::IntCoord(4, 3 + i * lineHeight, mTrainingOptions->getWidth() - 10, lineHeight), MyGUI::Align::Default); button->setUserData(skills[i].first); button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected); button->setCaptionWithReplacing( - MyGUI::TextIterator::toTagsString(skill->mName) + " - " + MyGUI::utility::toString(price)); + MyGUI::TextIterator::toTagsString(skill->mName) + " - " + MyGUI::utility::toString(price) + "#{sgp}"); button->setSize(button->getTextSize().width + 12, button->getSize().height); diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 1af7acea36..5ecd3bd1fa 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -1,13 +1,7 @@ #include "animationbindings.hpp" -#include -#include -#include #include -#include #include -#include -#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -15,11 +9,8 @@ #include "../mwmechanics/character.hpp" -#include "../mwworld/esmstore.hpp" - #include "context.hpp" #include "luamanagerimp.hpp" -#include "objectvariant.hpp" namespace MWLua { diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index 218d05b804..130144ab9b 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -8,7 +8,7 @@ #include "../mwbase/environment.hpp" #include "idcollectionbindings.hpp" -#include "types/types.hpp" +#include "recordstore.hpp" namespace sol { diff --git a/apps/openmw/mwlua/birthsignbindings.hpp b/apps/openmw/mwlua/birthsignbindings.hpp index bf41707d47..e57d56c971 100644 --- a/apps/openmw/mwlua/birthsignbindings.hpp +++ b/apps/openmw/mwlua/birthsignbindings.hpp @@ -3,10 +3,10 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + sol::table initBirthSignRecordBindings(const Context& context); } diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index ec64a3cddd..4e4542d89f 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -1,7 +1,5 @@ #include "cellbindings.hpp" -#include - #include #include #include @@ -27,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -38,7 +35,6 @@ #include #include #include -#include #include #include #include diff --git a/apps/openmw/mwlua/cellbindings.hpp b/apps/openmw/mwlua/cellbindings.hpp index 0d8e90e989..70255d8d7f 100644 --- a/apps/openmw/mwlua/cellbindings.hpp +++ b/apps/openmw/mwlua/cellbindings.hpp @@ -1,12 +1,12 @@ #ifndef MWLUA_CELLBINDINGS_H #define MWLUA_CELLBINDINGS_H -#include "context.hpp" - namespace MWLua { - void initCellBindingsForLocalScripts(const Context&); - void initCellBindingsForGlobalScripts(const Context&); + struct Context; + + void initCellBindingsForLocalScripts(const Context& context); + void initCellBindingsForGlobalScripts(const Context& context); } #endif // MWLUA_CELLBINDINGS_H diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index c94631fb3c..338698bfee 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -4,7 +4,7 @@ #include #include "idcollectionbindings.hpp" -#include "types/types.hpp" +#include "recordstore.hpp" namespace sol { diff --git a/apps/openmw/mwlua/classbindings.hpp b/apps/openmw/mwlua/classbindings.hpp index 1acb0a9ad3..cc67f2df02 100644 --- a/apps/openmw/mwlua/classbindings.hpp +++ b/apps/openmw/mwlua/classbindings.hpp @@ -3,10 +3,10 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + sol::table initClassRecordBindings(const Context& context); } diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index bdf71710af..282a9f970b 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -20,6 +19,7 @@ #include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" +#include "context.hpp" #include "coremwscriptbindings.hpp" #include "dialoguebindings.hpp" #include "factionbindings.hpp" diff --git a/apps/openmw/mwlua/corebindings.hpp b/apps/openmw/mwlua/corebindings.hpp index ef385ca993..6d27dd0412 100644 --- a/apps/openmw/mwlua/corebindings.hpp +++ b/apps/openmw/mwlua/corebindings.hpp @@ -3,13 +3,13 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + void addCoreTimeBindings(sol::table& api, const Context& context); - sol::table initCorePackage(const Context&); + sol::table initCorePackage(const Context& context); } #endif // MWLUA_COREBINDINGS_H diff --git a/apps/openmw/mwlua/debugbindings.hpp b/apps/openmw/mwlua/debugbindings.hpp index c508b54496..d7902cb618 100644 --- a/apps/openmw/mwlua/debugbindings.hpp +++ b/apps/openmw/mwlua/debugbindings.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWLUA_DEBUGBINDINGS_H #define OPENMW_MWLUA_DEBUGBINDINGS_H -#include +#include namespace MWLua { diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 6c652bccba..be7d249bfc 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -113,6 +113,15 @@ namespace MWLua scripts->onSkillLevelUp(event.mSkill, event.mSource); } + void operator()(const OnJailTimeServed& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onJailTimeServed(event.mDays); + } + private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index fb9183eb7c..407739d60e 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -70,8 +70,13 @@ namespace MWLua std::string mSkill; std::string mSource; }; + struct OnJailTimeServed + { + ESM::RefNum mActor; + int mDays; + }; using Event = std::variant; + OnAnimationTextKey, OnSkillUse, OnSkillLevelUp, OnJailTimeServed>; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/factionbindings.hpp b/apps/openmw/mwlua/factionbindings.hpp index 0dc06ceaf2..280c7d4791 100644 --- a/apps/openmw/mwlua/factionbindings.hpp +++ b/apps/openmw/mwlua/factionbindings.hpp @@ -3,10 +3,10 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + sol::table initCoreFactionBindings(const Context& context); } diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 7cb727a83e..6f9b82d8a9 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -14,6 +14,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwinput/actions.hpp" +#include "context.hpp" #include "luamanagerimp.hpp" namespace sol diff --git a/apps/openmw/mwlua/inputbindings.hpp b/apps/openmw/mwlua/inputbindings.hpp index 195f69f5f9..e9a19ed34d 100644 --- a/apps/openmw/mwlua/inputbindings.hpp +++ b/apps/openmw/mwlua/inputbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initInputPackage(const Context&); + struct Context; + + sol::table initInputPackage(const Context& context); } #endif // MWLUA_INPUTBINDINGS_H diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 3f85e2b066..2b053802db 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -1,14 +1,24 @@ #include "landbindings.hpp" +#include +#include + +#include +#include +#include + #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/worldmodel.hpp" + +#include "context.hpp" #include "object.hpp" namespace diff --git a/apps/openmw/mwlua/landbindings.hpp b/apps/openmw/mwlua/landbindings.hpp index 8cdf30046b..f38d260759 100644 --- a/apps/openmw/mwlua/landbindings.hpp +++ b/apps/openmw/mwlua/landbindings.hpp @@ -1,10 +1,12 @@ #ifndef MWLUA_LANDBINDINGS_H #define MWLUA_LANDBINDINGS_H -#include "context.hpp" +#include namespace MWLua { + struct Context; + sol::table initCoreLandBindings(const Context& context); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index d784328035..99c994304b 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -1,9 +1,5 @@ #include "localscripts.hpp" -#include -#include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/aicombat.hpp" @@ -232,7 +228,7 @@ namespace MWLua [&](LuaUtil::LuaView& view) { addPackage("openmw.self", sol::make_object(view.sol(), &mData)); }); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse, - &mOnSkillLevelUp }); + &mOnSkillLevelUp, &mOnJailTimeServed }); } void LocalScripts::setActive(bool active, bool callHandlers) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index b3ec647d0e..c51d2d8c19 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -1,9 +1,7 @@ #ifndef MWLUA_LOCALSCRIPTS_H #define MWLUA_LOCALSCRIPTS_H -#include -#include -#include +#include #include #include @@ -89,6 +87,7 @@ namespace MWLua { callEngineHandlers(mOnSkillLevelUp, skillId, source); } + void onJailTimeServed(int days) { callEngineHandlers(mOnJailTimeServed, days); } void applyStatsCache(); @@ -118,6 +117,7 @@ namespace MWLua EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" }; EngineHandlerList mOnSkillUse{ "_onSkillUse" }; EngineHandlerList mOnSkillLevelUp{ "_onSkillLevelUp" }; + EngineHandlerList mOnJailTimeServed{ "_onJailTimeServed" }; }; } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 749987e5b2..e7b73dbca1 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -1,29 +1,30 @@ #ifndef MWLUA_LUABINDINGS_H #define MWLUA_LUABINDINGS_H -#include #include -#include -#include "context.hpp" +#include +#include namespace MWLua { + struct Context; + // Initialize Lua packages that are available for all scripts. - std::map initCommonPackages(const Context&); + std::map initCommonPackages(const Context& context); // Initialize Lua packages that are available for global scripts (additionally to common packages). - std::map initGlobalPackages(const Context&); + std::map initGlobalPackages(const Context& context); // Initialize Lua packages that are available for local scripts (additionally to common packages). - std::map initLocalPackages(const Context&); + std::map initLocalPackages(const Context& context); // Initialize Lua packages that are available only for local scripts on the player (additionally to common and local // packages). - std::map initPlayerPackages(const Context&); + std::map initPlayerPackages(const Context& context); // Initialize Lua packages that are available only for menu scripts (additionally to common packages). - std::map initMenuPackages(const Context&); + std::map initMenuPackages(const Context& context); } #endif // MWLUA_LUABINDINGS_H diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 9c2778e55d..66ecfb1c55 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -5,6 +5,10 @@ #include #include +#include +#include +#include + #include #include @@ -15,7 +19,6 @@ #include -#include #include #include @@ -212,13 +215,12 @@ namespace MWLua // Run engine handlers mEngineEvents.callEngineHandlers(); - if (!timeManager.isPaused()) - { - float frameDuration = MWBase::Environment::get().getFrameDuration(); - for (LocalScripts* scripts : mActiveLocalScripts) - scripts->update(frameDuration); - mGlobalScripts.update(frameDuration); - } + bool isPaused = timeManager.isPaused(); + + float frameDuration = MWBase::Environment::get().getFrameDuration(); + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->update(isPaused ? 0 : frameDuration); + mGlobalScripts.update(isPaused ? 0 : frameDuration); mLua.protectedCall([&](LuaUtil::LuaView& lua) { mScriptTracker.unloadInactiveScripts(lua); }); } @@ -491,6 +493,11 @@ namespace MWLua EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) }); } + void LuaManager::jailTimeServed(const MWWorld::Ptr& actor, int days) + { + mEngineEvents.addToQueue(EngineEvents::OnJailTimeServed{ getId(actor), days }); + } + void LuaManager::onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth, const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType sourceType) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index f47b6f96cf..42b18d236f 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -3,9 +3,10 @@ #include #include -#include #include +#include + #include #include #include @@ -92,6 +93,7 @@ namespace MWLua bool loopfallback) override; void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override; void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) override; + void jailTimeServed(const MWWorld::Ptr& actor, int days) override; void onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth, const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType sourceType) override; diff --git a/apps/openmw/mwlua/magicbindings.hpp b/apps/openmw/mwlua/magicbindings.hpp index 047bd2e3d9..d9c5182d76 100644 --- a/apps/openmw/mwlua/magicbindings.hpp +++ b/apps/openmw/mwlua/magicbindings.hpp @@ -3,10 +3,10 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + sol::table initCoreMagicBindings(const Context& context); void addActorMagicBindings(sol::table& actor, const Context& context); } diff --git a/apps/openmw/mwlua/markupbindings.hpp b/apps/openmw/mwlua/markupbindings.hpp index 9105ab5edf..ec4b6dbbb2 100644 --- a/apps/openmw/mwlua/markupbindings.hpp +++ b/apps/openmw/mwlua/markupbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initMarkupPackage(const Context&); + struct Context; + + sol::table initMarkupPackage(const Context& context); } #endif // MWLUA_MARKUPBINDINGS_H diff --git a/apps/openmw/mwlua/menuscripts.cpp b/apps/openmw/mwlua/menuscripts.cpp index 32520c0822..a7ff80e63e 100644 --- a/apps/openmw/mwlua/menuscripts.cpp +++ b/apps/openmw/mwlua/menuscripts.cpp @@ -7,6 +7,8 @@ #include "../mwbase/statemanager.hpp" #include "../mwstate/character.hpp" +#include "context.hpp" + namespace MWLua { static const MWState::Character* findCharacter(std::string_view characterDir) diff --git a/apps/openmw/mwlua/menuscripts.hpp b/apps/openmw/mwlua/menuscripts.hpp index 8721224413..11c6cc1912 100644 --- a/apps/openmw/mwlua/menuscripts.hpp +++ b/apps/openmw/mwlua/menuscripts.hpp @@ -9,11 +9,11 @@ #include "../mwbase/luamanager.hpp" -#include "context.hpp" #include "inputprocessor.hpp" namespace MWLua { + struct Context; sol::table initMenuPackage(const Context& context); diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index 90d24b39fe..179b35aa5c 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -12,6 +12,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/worldimp.hpp" +#include "context.hpp" #include "object.hpp" #include diff --git a/apps/openmw/mwlua/mwscriptbindings.hpp b/apps/openmw/mwlua/mwscriptbindings.hpp index 9598f051ae..c0c88ec28c 100644 --- a/apps/openmw/mwlua/mwscriptbindings.hpp +++ b/apps/openmw/mwlua/mwscriptbindings.hpp @@ -3,13 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; - sol::table initMWScriptBindings(const Context&); - + sol::table initMWScriptBindings(const Context& context); } #endif // MWLUA_MWSCRIPTBINDINGS_H diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 6c244a0fd4..b31a934a54 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -13,9 +13,12 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/scene.hpp" +#include "context.hpp" #include "luamanagerimp.hpp" #include "objectlists.hpp" +#include + namespace { template diff --git a/apps/openmw/mwlua/nearbybindings.hpp b/apps/openmw/mwlua/nearbybindings.hpp index ee0022898e..2d8140195e 100644 --- a/apps/openmw/mwlua/nearbybindings.hpp +++ b/apps/openmw/mwlua/nearbybindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initNearbyPackage(const Context&); + struct Context; + + sol::table initNearbyPackage(const Context& context); } #endif // MWLUA_NEARBYBINDINGS_H diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index d032515314..4a50dee8b6 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -1,9 +1,7 @@ #ifndef MWLUA_OBJECT_H #define MWLUA_OBJECT_H -#include #include -#include #include diff --git a/apps/openmw/mwlua/objectbindings.hpp b/apps/openmw/mwlua/objectbindings.hpp index 5f69c54da3..3861211357 100644 --- a/apps/openmw/mwlua/objectbindings.hpp +++ b/apps/openmw/mwlua/objectbindings.hpp @@ -1,12 +1,12 @@ #ifndef MWLUA_OBJECTBINDINGS_H #define MWLUA_OBJECTBINDINGS_H -#include "context.hpp" - namespace MWLua { - void initObjectBindingsForLocalScripts(const Context&); - void initObjectBindingsForGlobalScripts(const Context&); + struct Context; + + void initObjectBindingsForLocalScripts(const Context& context); + void initObjectBindingsForGlobalScripts(const Context& context); } #endif // MWLUA_OBJECTBINDINGS_H diff --git a/apps/openmw/mwlua/objectlists.cpp b/apps/openmw/mwlua/objectlists.cpp index d0bda5a644..b0169dd651 100644 --- a/apps/openmw/mwlua/objectlists.cpp +++ b/apps/openmw/mwlua/objectlists.cpp @@ -1,9 +1,5 @@ #include "objectlists.hpp" -#include -#include -#include - #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwlua/postprocessingbindings.cpp b/apps/openmw/mwlua/postprocessingbindings.cpp index f12bda8650..6d0c33848a 100644 --- a/apps/openmw/mwlua/postprocessingbindings.cpp +++ b/apps/openmw/mwlua/postprocessingbindings.cpp @@ -8,6 +8,7 @@ #include "../mwbase/world.hpp" #include "../mwrender/postprocessor.hpp" +#include "context.hpp" #include "luamanagerimp.hpp" namespace diff --git a/apps/openmw/mwlua/postprocessingbindings.hpp b/apps/openmw/mwlua/postprocessingbindings.hpp index 50cd84fa24..55b46d2524 100644 --- a/apps/openmw/mwlua/postprocessingbindings.hpp +++ b/apps/openmw/mwlua/postprocessingbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initPostprocessingPackage(const Context&); + struct Context; + + sol::table initPostprocessingPackage(const Context& context); } #endif // MWLUA_POSTPROCESSINGBINDINGS_H diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp index b5c4d6093f..590d16b4c9 100644 --- a/apps/openmw/mwlua/racebindings.cpp +++ b/apps/openmw/mwlua/racebindings.cpp @@ -8,7 +8,7 @@ #include "../mwworld/esmstore.hpp" #include "idcollectionbindings.hpp" -#include "types/types.hpp" +#include "recordstore.hpp" namespace { diff --git a/apps/openmw/mwlua/racebindings.hpp b/apps/openmw/mwlua/racebindings.hpp index 43ba9237c5..405a158899 100644 --- a/apps/openmw/mwlua/racebindings.hpp +++ b/apps/openmw/mwlua/racebindings.hpp @@ -3,10 +3,10 @@ #include -#include "context.hpp" - namespace MWLua { + struct Context; + sol::table initRaceRecordBindings(const Context& context); } diff --git a/apps/openmw/mwlua/recordstore.hpp b/apps/openmw/mwlua/recordstore.hpp index aed84a1271..f2fb4ed453 100644 --- a/apps/openmw/mwlua/recordstore.hpp +++ b/apps/openmw/mwlua/recordstore.hpp @@ -1,10 +1,16 @@ #ifndef MWLUA_RECORDSTORE_H #define MWLUA_RECORDSTORE_H -#include +#include + +#include +#include +#include +#include +#include +#include +#include -#include -#include #include #include "apps/openmw/mwbase/environment.hpp" diff --git a/apps/openmw/mwlua/soundbindings.hpp b/apps/openmw/mwlua/soundbindings.hpp index 333ed898c4..d7af17cb69 100644 --- a/apps/openmw/mwlua/soundbindings.hpp +++ b/apps/openmw/mwlua/soundbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initCoreSoundBindings(const Context&); + struct Context; + + sol::table initCoreSoundBindings(const Context& context); sol::table initAmbientPackage(const Context& context); } diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 637ae0b002..8b103d5943 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -1,9 +1,11 @@ #include "stats.hpp" #include -#include #include +#include #include +#include +#include #include #include diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 826338ca7d..8d9892005c 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -108,6 +108,10 @@ namespace MWLua } luaManager->addUIMessage(message, mode); }; + + api["_showInteractiveMessage"] = [windowManager](std::string_view message, sol::optional) { + windowManager->interactiveMessageBox(message, { "#{Interface:OK}" }); + }; api["CONSOLE_COLOR"] = LuaUtil::makeStrictReadOnly(LuaUtil::tableFromPairs(lua, { { "Default", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Default.substr(1)) }, diff --git a/apps/openmw/mwlua/uibindings.hpp b/apps/openmw/mwlua/uibindings.hpp index 930ba7f3d8..32fb54e590 100644 --- a/apps/openmw/mwlua/uibindings.hpp +++ b/apps/openmw/mwlua/uibindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initUserInterfacePackage(const Context&); + struct Context; + + sol::table initUserInterfacePackage(const Context& context); } #endif // MWLUA_UIBINDINGS_H diff --git a/apps/openmw/mwlua/userdataserializer.hpp b/apps/openmw/mwlua/userdataserializer.hpp index f52bb22723..35e4c4729b 100644 --- a/apps/openmw/mwlua/userdataserializer.hpp +++ b/apps/openmw/mwlua/userdataserializer.hpp @@ -1,7 +1,8 @@ #ifndef MWLUA_USERDATASERIALIZER_H #define MWLUA_USERDATASERIALIZER_H -#include "object.hpp" +#include +#include namespace LuaUtil { diff --git a/apps/openmw/mwlua/vfsbindings.hpp b/apps/openmw/mwlua/vfsbindings.hpp index b251db6fd4..ab3ad3662c 100644 --- a/apps/openmw/mwlua/vfsbindings.hpp +++ b/apps/openmw/mwlua/vfsbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initVFSPackage(const Context&); + struct Context; + + sol::table initVFSPackage(const Context& context); } #endif // MWLUA_VFSBINDINGS_H diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index c02bad3bd3..d98c769e65 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -27,6 +26,7 @@ #include "luamanagerimp.hpp" #include "animationbindings.hpp" +#include "context.hpp" #include "corebindings.hpp" #include "mwscriptbindings.hpp" diff --git a/apps/openmw/mwlua/worldbindings.hpp b/apps/openmw/mwlua/worldbindings.hpp index 4bd2318b68..368344429b 100644 --- a/apps/openmw/mwlua/worldbindings.hpp +++ b/apps/openmw/mwlua/worldbindings.hpp @@ -3,11 +3,11 @@ #include -#include "context.hpp" - namespace MWLua { - sol::table initWorldPackage(const Context&); + struct Context; + + sol::table initWorldPackage(const Context& context); } #endif // MWLUA_WORLDBINDINGS_H diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f403f97c2f..adc107f7cc 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -93,9 +93,10 @@ namespace namespace MWPhysics { PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) - : mShapeManager( - std::make_unique(resourceSystem->getVFS(), resourceSystem->getSceneManager(), - resourceSystem->getNifFileManager(), Settings::cells().mCacheExpiryDelay)) + : mPhysicsDt(1.f / 60.f) + , mShapeManager(std::make_unique(resourceSystem->getVFS(), + resourceSystem->getSceneManager(), resourceSystem->getNifFileManager(), + Settings::cells().mCacheExpiryDelay)) , mResourceSystem(resourceSystem) , mDebugDrawEnabled(false) , mTimeAccum(0.0f) @@ -103,7 +104,6 @@ namespace MWPhysics , mWaterHeight(0) , mWaterEnabled(false) , mParentNode(std::move(parentNode)) - , mPhysicsDt(1.f / 60.f) { mResourceSystem->addResourceManager(mShapeManager.get()); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 8a845b4c41..0ab0cf91a0 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -287,6 +287,8 @@ namespace MWPhysics void reportStats(unsigned int frameNumber, osg::Stats& stats) const; void reportCollision(const btVector3& position, const btVector3& normal); + float mPhysicsDt; + private: void updateWater(); @@ -330,8 +332,6 @@ namespace MWPhysics osg::ref_ptr mParentNode; - float mPhysicsDt; - std::size_t mSimulationsCounter = 0; std::array, 2> mSimulations; std::vector> mActorsPositions; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index d22e12ff01..5912895855 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1117,8 +1117,8 @@ namespace MWRender return keyframeController->getAsCallback(); } - - return asCallback; + else + return asCallback; } void Animation::resetActiveGroups() diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index c14eee27e4..4aface83d6 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -401,14 +401,12 @@ namespace MWRender { if (mViewMode == VM_FirstPerson) { - static bool prototypeAdded = false; - if (!prototypeAdded) - { + [[maybe_unused]] static const bool prototypeAdded = [&] { osg::ref_ptr depthClearBin(new osgUtil::RenderBin); depthClearBin->setDrawCallback(new DepthClearCallback()); osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); - prototypeAdded = true; - } + return true; + }(); mObjectRoot->getOrCreateStateSet()->setRenderBinDetails( RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); } diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index ab982d0c55..b19863d695 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -88,14 +88,13 @@ namespace MWRender if (mProgramBlobber != nullptr) { - static bool pipelineLogged = [&] { + [[maybe_unused]] static const bool pipelineLogged = [&] { if (mUseCompute) Log(Debug::Info) << "Initialized compute shader pipeline for water ripples"; else Log(Debug::Info) << "Initialized fallback fragment shader pipeline for water ripples"; return true; }(); - (void)pipelineLogged; } setCullCallback(new osg::NodeCallback); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 9776d7e632..0e752701c7 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,6 +1,8 @@ #include "terrainstorage.hpp" #include +#include +#include #include #include "../mwbase/environment.hpp" @@ -111,4 +113,15 @@ namespace MWRender return esmStore.get().search(index, plugin); } + const ESM4::LandTexture* TerrainStorage::getEsm4LandTexture(ESM::RefId ltexId) const + { + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + return esmStore.get().search(ltexId); + } + + const ESM4::TextureSet* TerrainStorage::getEsm4TextureSet(ESM::RefId txstId) const + { + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + return esmStore.get().search(txstId); + } } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 731f396713..406894fd9b 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -24,6 +24,9 @@ namespace MWRender osg::ref_ptr getLand(ESM::ExteriorCellLocation cellLocation) override; const std::string* getLandTexture(std::uint16_t index, int plugin) override; + const ESM4::LandTexture* getEsm4LandTexture(ESM::RefId ltexId) const override; + const ESM4::TextureSet* getEsm4TextureSet(ESM::RefId txstId) const override; + bool hasData(ESM::ExteriorCellLocation cellLocation) override; /// Get bounds of the whole terrain in cell units diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index af81fd2d3c..1511909332 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -604,7 +604,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - std::string_view effect = runtime.getStringLiteral(runtime[0].mInteger); + const std::string_view effectName = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (!ptr.getClass().isActor()) @@ -615,11 +615,11 @@ namespace MWScript long key; - if (const auto k = ::Misc::StringUtils::toNumeric(effect.data()); + if (const auto k = ::Misc::StringUtils::toNumeric(effectName); k.has_value() && *k >= 0 && *k <= 32767) key = *k; else - key = ESM::MagicEffect::effectGmstIdToIndex(effect); + key = ESM::MagicEffect::effectGmstIdToIndex(effectName); const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); for (const auto& spell : stats.getActiveSpells()) diff --git a/apps/openmw/mwsound/ffmpegdecoder.cpp b/apps/openmw/mwsound/ffmpegdecoder.cpp index 5a0f336a93..f8b40ddabb 100644 --- a/apps/openmw/mwsound/ffmpegdecoder.cpp +++ b/apps/openmw/mwsound/ffmpegdecoder.cpp @@ -522,16 +522,14 @@ namespace MWSound /* We need to make sure ffmpeg is initialized. Optionally silence warning * output from the lib */ - static bool done_init = false; - if (!done_init) - { + [[maybe_unused]] static const bool doneInit = [] { // This is not needed anymore above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 av_register_all(); #endif av_log_set_level(AV_LOG_ERROR); - done_init = true; - } + return true; + }(); } FFmpegDecoder::~FFmpegDecoder() diff --git a/apps/openmw/mwsound/openaloutput.cpp b/apps/openmw/mwsound/openaloutput.cpp index 60c9e5f3ea..ecce476685 100644 --- a/apps/openmw/mwsound/openaloutput.cpp +++ b/apps/openmw/mwsound/openaloutput.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "efxpresets.h" @@ -963,6 +964,7 @@ namespace MWSound // Speed of sound is in units per second. Take the sound speed in air (assumed // meters per second), multiply by the units per meter to get the speed in u/s. alSpeedOfSound(Constants::SoundSpeedInAir * Constants::UnitsPerMeter); + alDopplerFactor(Settings::sound().mDopplerFactor); alGetError(); mInitialized = true; @@ -1142,8 +1144,8 @@ namespace MWSound alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } - void OpenALOutput::initCommon3D(ALuint source, const osg::Vec3f& pos, ALfloat mindist, ALfloat maxdist, - ALfloat gain, ALfloat pitch, bool loop, bool useenv) + void OpenALOutput::initCommon3D(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat mindist, + ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv) { alSourcef(source, AL_REFERENCE_DISTANCE, mindist); alSourcef(source, AL_MAX_DISTANCE, maxdist); @@ -1179,11 +1181,11 @@ namespace MWSound alSourcef(source, AL_PITCH, pitch); alSourcefv(source, AL_POSITION, pos.ptr()); alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSourcefv(source, AL_VELOCITY, vel.ptr()); } - void OpenALOutput::updateCommon( - ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv) + void OpenALOutput::updateCommon(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat maxdist, + ALfloat gain, ALfloat pitch, bool useenv) { if (useenv && mListenerEnv == Env_Underwater && !mWaterFilter) { @@ -1195,7 +1197,7 @@ namespace MWSound alSourcef(source, AL_PITCH, pitch); alSourcefv(source, AL_POSITION, pos.ptr()); alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSourcefv(source, AL_VELOCITY, vel.ptr()); } bool OpenALOutput::playSound(Sound* sound, Sound_Handle data, float offset) @@ -1248,8 +1250,9 @@ namespace MWSound } source = mFreeSources.front(); - initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), - sound->getRealVolume(), getTimeScaledPitch(sound), sound->getIsLooping(), sound->getUseEnv()); + initCommon3D(source, sound->getPosition(), sound->getVelocity(), sound->getMinDistance(), + sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), sound->getIsLooping(), + sound->getUseEnv()); alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcef(source, AL_SEC_OFFSET, offset); if (getALError() != AL_NO_ERROR) @@ -1312,8 +1315,8 @@ namespace MWSound return; ALuint source = GET_PTRID(sound->mHandle); - updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), - getTimeScaledPitch(sound), sound->getUseEnv()); + updateCommon(source, sound->getPosition(), sound->getVelocity(), sound->getMaxDistance(), + sound->getRealVolume(), getTimeScaledPitch(sound), sound->getUseEnv()); getALError(); } @@ -1360,8 +1363,8 @@ namespace MWSound if (sound->getIsLooping()) Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; - initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), - sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv()); + initCommon3D(source, sound->getPosition(), sound->getVelocity(), sound->getMinDistance(), + sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv()); if (getALError() != AL_NO_ERROR) return false; @@ -1443,8 +1446,8 @@ namespace MWSound OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); ALuint source = stream->mSource; - updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), - getTimeScaledPitch(sound), sound->getUseEnv()); + updateCommon(source, sound->getPosition(), sound->getVelocity(), sound->getMaxDistance(), + sound->getRealVolume(), getTimeScaledPitch(sound), sound->getUseEnv()); getALError(); } @@ -1459,12 +1462,13 @@ namespace MWSound } void OpenALOutput::updateListener( - const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env) + const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, const osg::Vec3f& vel, Environment env) { if (mContext) { ALfloat orient[6] = { atdir.x(), atdir.y(), atdir.z(), updir.x(), updir.y(), updir.z() }; alListenerfv(AL_POSITION, pos.ptr()); + alListenerfv(AL_VELOCITY, vel.ptr()); alListenerfv(AL_ORIENTATION, orient); if (env != mListenerEnv) @@ -1497,6 +1501,7 @@ namespace MWSound } mListenerPos = pos; + mListenerVel = vel; mListenerEnv = env; } @@ -1582,7 +1587,6 @@ namespace MWSound : SoundOutput(mgr) , mDevice(nullptr) , mContext(nullptr) - , mListenerPos(0.0f, 0.0f, 0.0f) , mListenerEnv(Env_Normal) , mWaterFilter(0) , mWaterEffect(0) diff --git a/apps/openmw/mwsound/openaloutput.hpp b/apps/openmw/mwsound/openaloutput.hpp index 4e96dd1627..d689d08546 100644 --- a/apps/openmw/mwsound/openaloutput.hpp +++ b/apps/openmw/mwsound/openaloutput.hpp @@ -46,6 +46,7 @@ namespace MWSound StreamVec mActiveStreams; osg::Vec3f mListenerPos; + osg::Vec3f mListenerVel; Environment mListenerEnv; ALuint mWaterFilter; @@ -64,11 +65,11 @@ namespace MWSound std::unique_ptr mDefaultDeviceThread; void initCommon2D(ALuint source, const osg::Vec3f& pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv); - void initCommon3D(ALuint source, const osg::Vec3f& pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, - ALfloat pitch, bool loop, bool useenv); + void initCommon3D(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat mindist, ALfloat maxdist, + ALfloat gain, ALfloat pitch, bool loop, bool useenv); - void updateCommon( - ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv); + void updateCommon(ALuint source, const osg::Vec3f& pos, const osg::Vec3f& vel, ALfloat maxdist, ALfloat gain, + ALfloat pitch, bool useenv); float getTimeScaledPitch(SoundBase* sound); @@ -108,8 +109,8 @@ namespace MWSound void startUpdate() override; void finishUpdate() override; - void updateListener( - const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env) override; + void updateListener(const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, + const osg::Vec3f& vel, Environment env) override; void pauseSounds(int types) override; void resumeSounds(int types) override; diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 5160b2934f..7d020c31f9 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -31,6 +31,8 @@ namespace MWSound struct SoundParams { osg::Vec3f mPos; + osg::Vec3f mLastPos; + osg::Vec3f mVel; float mVolume = 1.0f; float mBaseVolume = 1.0f; float mPitch = 1.0f; @@ -57,6 +59,8 @@ namespace MWSound public: void setPosition(const osg::Vec3f& pos) { mParams.mPos = pos; } + void setLastPosition(const osg::Vec3f& lastpos) { mParams.mLastPos = lastpos; } + void setVelocity(const osg::Vec3f& vel) { mParams.mVel = vel; } void setVolume(float volume) { mParams.mVolume = volume; } void setBaseVolume(float volume) { mParams.mBaseVolume = volume; } void setFadeout(float duration) { setFade(duration, 0.0, Play_StopAtFadeEnd); } @@ -150,6 +154,8 @@ namespace MWSound } const osg::Vec3f& getPosition() const { return mParams.mPos; } + const osg::Vec3f& getLastPosition() const { return mParams.mLastPos; } + const osg::Vec3f& getVelocity() const { return mParams.mVel; } float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume * mParams.mFadeVolume; } float getPitch() const { return mParams.mPitch; } float getMinDistance() const { return mParams.mMinDistance; } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 66bdfbdbfa..86631448a9 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -119,6 +119,7 @@ namespace MWSound , mListenerPos(0, 0, 0) , mListenerDir(1, 0, 0) , mListenerUp(0, 0, 1) + , mListenerVel(0, 0, 0) , mUnderwaterSound(nullptr) , mNearWaterSound(nullptr) , mPlaybackPaused(false) @@ -960,7 +961,7 @@ namespace MWSound } mOutput->startUpdate(); - mOutput->updateListener(mListenerPos, mListenerDir, mListenerUp, env); + mOutput->updateListener(mListenerPos, mListenerDir, mListenerUp, mListenerVel, env); updateMusic(duration); @@ -977,7 +978,13 @@ namespace MWSound if (sound->getIs3D()) { if (!ptr.isEmpty()) + { + sound->setLastPosition(sound->getPosition()); sound->setPosition(ptr.getRefData().getPosition().asVec3()); + MWBase::World* world = MWBase::Environment::get().getWorld(); + sound->setVelocity( + (sound->getPosition() - sound->getLastPosition()) / world->getPhysicsFrameRateDt()); + } cull3DSound(sound); } @@ -1013,8 +1020,11 @@ namespace MWSound { if (!ptr.isEmpty()) { + sound->setLastPosition(sound->getPosition()); MWBase::World* world = MWBase::Environment::get().getWorld(); sound->setPosition(world->getActorHeadTransform(ptr).getTrans()); + sound->setVelocity( + (sound->getPosition() - sound->getLastPosition()) / world->getPhysicsFrameRateDt()); } cull3DSound(sound); @@ -1153,6 +1163,11 @@ namespace MWSound mWaterSoundUpdater.setUnderwater(underwater); } + void SoundManager::setListenerVel(const osg::Vec3f& vel) + { + mListenerVel = vel; + } + void SoundManager::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) { SoundMap::iterator snditer = mActiveSounds.find(old.mRef); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 8fc7e6701f..f1b79557a0 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -92,6 +92,7 @@ namespace MWSound osg::Vec3f mListenerPos; osg::Vec3f mListenerDir; osg::Vec3f mListenerUp; + osg::Vec3f mListenerVel; int mPausedSoundTypes[BlockerType::MaxCount] = {}; @@ -283,6 +284,8 @@ namespace MWSound void setListenerPosDir( const osg::Vec3f& pos, const osg::Vec3f& dir, const osg::Vec3f& up, bool underwater) override; + void setListenerVel(const osg::Vec3f& vel) override; + void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) override; void clear() override; diff --git a/apps/openmw/mwsound/soundoutput.hpp b/apps/openmw/mwsound/soundoutput.hpp index e5c266b204..4b24f159d3 100644 --- a/apps/openmw/mwsound/soundoutput.hpp +++ b/apps/openmw/mwsound/soundoutput.hpp @@ -61,8 +61,8 @@ namespace MWSound virtual void startUpdate() = 0; virtual void finishUpdate() = 0; - virtual void updateListener( - const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env) + virtual void updateListener(const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, + const osg::Vec3f& vel, Environment env) = 0; virtual void pauseSounds(int types) = 0; diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 0c37f243e8..fc4ef2713b 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -110,6 +110,7 @@ namespace ESM4 struct Static; struct StaticCollection; struct Terminal; + struct TextureSet; struct Tree; struct Weapon; struct World; @@ -149,7 +150,7 @@ namespace MWWorld Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, - Store, Store, Store, Store>; + Store, Store, Store, Store, Store>; private: template diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 6ea510e1e2..c436f86df5 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -461,6 +461,11 @@ namespace MWWorld update(magicBoltState, duration); + for (const auto& sound : magicBoltState.mSounds) + { + sound->setVelocity(direction * speed); + } + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit // result. std::vector targetActors; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0c9a13bc47..1a68142c15 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1273,13 +1273,12 @@ namespace MWWorld const std::size_t leftCapacity = mPreloader->getMaxCacheSize() - mPreloader->getCacheSize(); if (cells.size() > leftCapacity) { - static bool logged = [&] { + [[maybe_unused]] static const bool logged = [&] { Log(Debug::Warning) << "Not enough cell preloader cache capacity to preload exterior cells, consider " "increasing \"preload cell cache max\" up to " << (mPreloader->getCacheSize() + cells.size()); return true; }(); - (void)logged; cells.resize(leftCapacity); } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 80bcdb056a..e4e67c2f3d 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1354,6 +1354,7 @@ template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c07f5b9161..d8bc768b39 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1460,6 +1460,8 @@ namespace MWWorld void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) { mPhysics->queueObjectMovement(ptr, velocity); + if (ptr == MWMechanics::getPlayer()) + MWBase::Environment::get().getSoundManager()->setListenerVel(velocity); } void World::updateAnimatedCollisionShape(const Ptr& ptr) @@ -3182,6 +3184,11 @@ namespace MWWorld return mWeatherManager->getSunPercentage(getTimeStamp().getHour()); } + float World::getPhysicsFrameRateDt() const + { + return mPhysics->mPhysicsDt; + } + bool World::findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) { if (cell->isExterior()) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 16f91177a1..dfa0c33ecf 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -584,6 +584,8 @@ namespace MWWorld float getSunVisibility() const override; float getSunPercentage() const override; + float getPhysicsFrameRateDt() const override; + bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) override; /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 8a3325ea71..6b7fe32d40 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -407,6 +407,7 @@ add_component_dir(detournavigator areatype asyncnavmeshupdater bounds + cellgridbounds changetype collisionshapetype commulativeaabb diff --git a/components/detournavigator/dbrefgeometryobject.hpp b/components/detournavigator/dbrefgeometryobject.hpp index dbbafe7e18..71e035d9e6 100644 --- a/components/detournavigator/dbrefgeometryobject.hpp +++ b/components/detournavigator/dbrefgeometryobject.hpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include diff --git a/components/detournavigator/generatenavmeshtile.cpp b/components/detournavigator/generatenavmeshtile.cpp index 118114181e..115ca75d40 100644 --- a/components/detournavigator/generatenavmeshtile.cpp +++ b/components/detournavigator/generatenavmeshtile.cpp @@ -10,10 +10,8 @@ #include -#include #include #include -#include #include namespace DetourNavigator @@ -79,7 +77,7 @@ namespace DetourNavigator return; } - const auto data + const std::unique_ptr data = prepareNavMeshTileData(*recastMesh, mWorldspace, mTilePosition, mAgentBounds, mSettings.mRecast); if (data == nullptr) diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index fe63d27a1e..06bb693aad 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -3,8 +3,6 @@ #include "exceptions.hpp" #include "flags.hpp" #include "navmeshdata.hpp" -#include "navmeshdb.hpp" -#include "navmeshtilescache.hpp" #include "offmeshconnection.hpp" #include "preparednavmeshdata.hpp" #include "recastcontext.hpp" @@ -22,8 +20,6 @@ #include #include -#include -#include namespace DetourNavigator { diff --git a/components/detournavigator/navmeshtileview.cpp b/components/detournavigator/navmeshtileview.cpp index d22efcbfff..e77962bb3c 100644 --- a/components/detournavigator/navmeshtileview.cpp +++ b/components/detournavigator/navmeshtileview.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include diff --git a/components/detournavigator/offmeshconnection.hpp b/components/detournavigator/offmeshconnection.hpp index fb8b166498..01bae02732 100644 --- a/components/detournavigator/offmeshconnection.hpp +++ b/components/detournavigator/offmeshconnection.hpp @@ -6,7 +6,6 @@ #include #include -#include namespace DetourNavigator { diff --git a/components/detournavigator/preparednavmeshdata.cpp b/components/detournavigator/preparednavmeshdata.cpp index a737ae19a5..32e559b8d0 100644 --- a/components/detournavigator/preparednavmeshdata.cpp +++ b/components/detournavigator/preparednavmeshdata.cpp @@ -4,8 +4,6 @@ #include -#include - namespace { void initPolyMeshDetail(rcPolyMeshDetail& value) noexcept diff --git a/components/detournavigator/raycast.hpp b/components/detournavigator/raycast.hpp index 855c5562fc..374b09320c 100644 --- a/components/detournavigator/raycast.hpp +++ b/components/detournavigator/raycast.hpp @@ -3,9 +3,10 @@ #include "flags.hpp" -#include #include +#include + class dtNavMeshQuery; namespace DetourNavigator diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 6d06db0799..1a67bb5495 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -11,9 +11,7 @@ #include #include -#include #include -#include #include #include diff --git a/components/esm/common.hpp b/components/esm/common.hpp index 90c60ad6ab..7d149a865c 100644 --- a/components/esm/common.hpp +++ b/components/esm/common.hpp @@ -31,6 +31,7 @@ namespace ESM VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues VER_094 = 0x3f70a3d7, // TES5/FO3 VER_170 = 0x3fd9999a, // TES5 + VER_171 = 0x3fdae148, // TES5 VER_095 = 0x3f733333, // FO4 }; diff --git a/components/esm/esmterrain.cpp b/components/esm/esmterrain.cpp index 4bc6768e51..ec824d5112 100644 --- a/components/esm/esmterrain.cpp +++ b/components/esm/esmterrain.cpp @@ -33,6 +33,7 @@ ESM::LandData::LandData(const ESM::Land& land, int loadFlags) , mNormals(mData->mNormals) , mColors(mData->mColours) , mTextures(mData->mTextures) + , mIsEsm4(false) { } @@ -43,9 +44,11 @@ ESM::LandData::LandData(const ESM4::Land& land, int /*loadFlags*/) , mMaxHeight(std::numeric_limits::lowest()) , mSize(Constants::ESM4CellSizeInUnits) , mLandSize(ESM4::Land::sVertsPerSide) + , mPlugin(land.mId.mContentFile) , mNormals(land.mVertNorm) , mColors(land.mVertColr) , mTextures(textures) + , mIsEsm4(true) { float rowOffset = land.mHeightMap.heightOffset; for (int y = 0; y < mLandSize; y++) @@ -69,6 +72,9 @@ ESM::LandData::LandData(const ESM4::Land& land, int /*loadFlags*/) } mHeights = mHeightsData; + + for (int i = 0; i < 4; ++i) + mEsm4Textures[i] = land.mTextures[i]; } namespace ESM diff --git a/components/esm/esmterrain.hpp b/components/esm/esmterrain.hpp index 991df54c8f..b6b62773d5 100644 --- a/components/esm/esmterrain.hpp +++ b/components/esm/esmterrain.hpp @@ -1,15 +1,13 @@ #ifndef COMPONENTS_ESM_ESMTERRAIN #define COMPONENTS_ESM_ESMTERRAIN +#include #include #include #include #include -namespace ESM4 -{ - struct Land; -} +#include namespace ESM { @@ -28,7 +26,6 @@ namespace ESM std::span getHeights() const { return mHeights; } std::span getNormals() const { return mNormals; } std::span getColors() const { return mColors; } - std::span getTextures() const { return mTextures; } float getSize() const { return mSize; } float getMinHeight() const { return mMinHeight; } float getMaxHeight() const { return mMaxHeight; } @@ -36,6 +33,22 @@ namespace ESM int getLoadFlags() const { return mLoadFlags; } int getPlugin() const { return mPlugin; } + bool isEsm4() const { return mIsEsm4; } + + std::span getTextures() const + { + if (mIsEsm4) + throw std::logic_error("ESM3 textures requested from ESM4 LandData"); + return mTextures; + } + + const ESM4::Land::Texture& getEsm4Texture(std::size_t quad) const + { + if (!mIsEsm4) + throw std::logic_error("ESM4 texture requested from ESM3 LandData"); + return mEsm4Textures[quad]; + } + private: std::unique_ptr mData; int mLoadFlags = 0; @@ -49,6 +62,8 @@ namespace ESM std::span mNormals; std::span mColors; std::span mTextures; + std::array mEsm4Textures; + bool mIsEsm4; }; } diff --git a/components/esm/formid.hpp b/components/esm/formid.hpp index e9416e35c7..9fe89585c2 100644 --- a/components/esm/formid.hpp +++ b/components/esm/formid.hpp @@ -51,10 +51,10 @@ namespace std { size_t operator()(const ESM::FormId& formId) const { - static_assert(sizeof(ESM::FormId) == sizeof(size_t)); - size_t s; - memcpy(&s, &formId, sizeof(size_t)); - return hash()(s); + static_assert(sizeof(ESM::FormId) == sizeof(uint64_t)); + uint64_t s; + memcpy(&s, &formId, sizeof(ESM::FormId)); + return hash()(s); } }; diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 0b76fab0ff..3b9f8ccd15 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -79,6 +79,7 @@ #include #include #include +#include #include #include diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index 53fb1de083..a92b2b5960 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + Copyright (C) 2015 - 2024 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -17,7 +17,7 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. - cc9cii cc9c@iinet.net.au + cc9cii cc9cii@hotmail.com Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by @@ -26,13 +26,51 @@ */ #include "loadland.hpp" +#include #include #include #include #include "reader.hpp" -// #include "writer.hpp" + +namespace +{ + void assignDefaultTextures(ESM4::Land& land, ESM4::Reader& reader) + { + std::uint32_t esmVer = reader.esmVersion(); + + // Note: in games after TES4 it can be configured in ini file (sDefaultLandDiffuseTexture) + if (!reader.hasFormVersion() && (esmVer == ESM::VER_080 || esmVer == ESM::VER_100)) // TES4 + { + land.mDefaultDiffuseMap = VFS::Path::NormalizedView("textures/landscape/terrainhddirt01.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/terrainhddirt01_n.dds"); + } + else if (reader.hasFormVersion() && reader.formVersion() >= 16 + && (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || esmVer == ESM::VER_171)) // TES5 + { + land.mDefaultDiffuseMap = VFS::Path::NormalizedView("textures/landscape/dirt02.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/dirt02_n.dds"); + } + else if (esmVer == ESM::VER_095 || esmVer == ESM::VER_100) // FO4 + { + land.mDefaultDiffuseMap + = VFS::Path::NormalizedView("textures/landscape/ground/commonwealthdefault01_d.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/ground/commonwealthdefault01_n.dds"); + } + else if (esmVer == ESM::VER_094 || esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134) + { // FO3, FONV + land.mDefaultDiffuseMap = VFS::Path::NormalizedView("textures/landscape/dirtwasteland01.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/dirtwasteland01_n.dds"); + } + else + { + // Nothing especially bad happens if default texture is not set (except of the missing texture of course), + // but we throw an error because this case is unexpected and detection logic needs to be updated. + throw std::runtime_error("ESM4::Land unknown ESM version"); + } + } +} // overlap north // @@ -53,12 +91,16 @@ void ESM4::Land::load(ESM4::Reader& reader) { mId = reader.getFormIdFromHeader(); mFlags = reader.hdr().record.flags; + mDataTypes = 0; mCell = reader.currCell(); TxtLayer layer; std::int8_t currentAddQuad = -1; // for VTXT following ATXT + assignDefaultTextures(*this, reader); - // std::map uniqueTextures; // FIXME: for temp testing only + layer.texture.formId = 0; + for (int i = 0; i < 4; ++i) + mTextures[i].base.formId = 0; while (reader.getSubRecordHeader()) { @@ -78,12 +120,6 @@ void ESM4::Land::load(ESM4::Reader& reader) } case ESM::fourCC("VHGT"): // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 { -#if 0 - reader.get(mHeightMap.heightOffset); - reader.get(mHeightMap.gradientData); - reader.get(mHeightMap.unknown1); - reader.get(mHeightMap.unknown2); -#endif reader.get(mHeightMap); mDataTypes |= LAND_VHGT; break; @@ -102,13 +138,9 @@ void ESM4::Land::load(ESM4::Reader& reader) if (base.quadrant >= 4) throw std::runtime_error("base texture quadrant index error"); - reader.adjustFormId(base.formId); - mTextures[base.quadrant].base = std::move(base); -#if 0 - std::cout << "Base Texture formid: 0x" - << std::hex << mTextures[base.quadrant].base.formId - << ", quad " << std::dec << (int)base.quadrant << std::endl; -#endif + if (base.formId != 0) + reader.adjustFormId(base.formId); + mTextures[base.quadrant].base = base; } break; } @@ -116,31 +148,23 @@ void ESM4::Land::load(ESM4::Reader& reader) { if (currentAddQuad != -1) { - // FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now - Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex; + // NOTE: sometimes there are no VTXT following an ATXT + layer.data.resize(1); // just one spot + layer.data.back().position = 0; // this corner + layer.data.back().opacity = 0.f; // transparent + + if (layer.texture.layerIndex != mTextures[currentAddQuad].layers.size()) + throw std::runtime_error("ESM4::LAND additional texture skipping layer"); + mTextures[currentAddQuad].layers.push_back(layer); } + reader.get(layer.texture); - reader.adjustFormId(layer.texture.formId); + if (layer.texture.formId != 0) + reader.adjustFormId(layer.texture.formId); if (layer.texture.quadrant >= 4) - throw std::runtime_error("additional texture quadrant index error"); -#if 0 - FormId txt = layer.texture.formId; - std::map::iterator lb = uniqueTextures.lower_bound(txt); - if (lb != uniqueTextures.end() && !(uniqueTextures.key_comp()(txt, lb->first))) - { - lb->second += 1; - } - else - uniqueTextures.insert(lb, std::make_pair(txt, 1)); -#endif -#if 0 - std::cout << "Additional Texture formId: 0x" - << std::hex << layer.texture.formId - << ", quad " << std::dec << (int)layer.texture.quadrant << std::endl; - std::cout << "Additional Texture layer: " - << std::dec << (int)layer.texture.layerIndex << std::endl; -#endif + throw std::runtime_error("ESM4::LAND additional texture quadrant index error"); + currentAddQuad = layer.texture.quadrant; break; } @@ -156,25 +180,17 @@ void ESM4::Land::load(ESM4::Reader& reader) if (count) { layer.data.resize(count); - std::vector::iterator it = layer.data.begin(); - for (; it != layer.data.end(); ++it) - { - reader.get(*it); - // FIXME: debug only - // std::cout << "pos: " << std::dec << (int)(*it).position << std::endl; - } + for (ESM4::Land::VTXT& vtxt : layer.data) + reader.get(vtxt); } - mTextures[currentAddQuad].layers.push_back(layer); - // Assumed that the layers are added in the correct sequence - // FIXME: Knights.esp doesn't seem to observe this - investigate more - // assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()-1 - //&& "additional texture layer index error"); + if (layer.texture.layerIndex != mTextures[currentAddQuad].layers.size()) + throw std::runtime_error("ESM4::LAND additional texture skipping layer"); + + mTextures[currentAddQuad].layers.push_back(layer); currentAddQuad = -1; layer.data.clear(); - // FIXME: debug only - // std::cout << "VTXT: count " << std::dec << count << std::endl; break; } case ESM::fourCC("VTEX"): // only in Oblivion? @@ -195,44 +211,14 @@ void ESM4::Land::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; default: - throw std::runtime_error("ESM4::LAND::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + throw std::runtime_error("ESM4::LAND - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } if (currentAddQuad != -1) { - // FIXME: not sure if it happens here as well + // not sure if it happens here as well, if so just ignore Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex << " quad " << static_cast(layer.texture.quadrant); - mTextures[currentAddQuad].layers.push_back(layer); } - - bool missing = false; - for (int i = 0; i < 4; ++i) - { - if (mTextures[i].base.formId == 0) - { - // std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " missing base, quad " << i << std::endl; - // std::cout << "layers " << mTextures[i].layers.size() << std::endl; - // NOTE: can't set the default here since FO3/FONV may have different defaults - // mTextures[i].base.formId = 0x000008C0; // TerrainHDDirt01.dds - missing = true; - } - // else - //{ - // std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " base, quad " << i << std::endl; - // std::cout << "layers " << mTextures[i].layers.size() << std::endl; - // } - } - // at least one of the quadrants do not have a base texture, return without setting the flag - if (!missing) - mDataTypes |= LAND_VTEX; } - -// void ESM4::Land::save(ESM4::Writer& writer) const -//{ -// } - -// void ESM4::Land::blank() -//{ -// } diff --git a/components/esm4/loadland.hpp b/components/esm4/loadland.hpp index ccfa009d80..600a235f1c 100644 --- a/components/esm4/loadland.hpp +++ b/components/esm4/loadland.hpp @@ -32,6 +32,7 @@ #include #include +#include namespace ESM4 { @@ -124,6 +125,8 @@ namespace ESM4 Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right std::vector mIds; // land texture (LTEX) formids ESM::FormId mCell; + VFS::Path::NormalizedView mDefaultDiffuseMap; + VFS::Path::NormalizedView mDefaultNormalMap; void load(Reader& reader); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 35ec814aa2..1a07f6fe0a 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -89,6 +91,8 @@ namespace ESMTerrain LandObject::LandObject(const ESM4::Land& land, int loadFlags) : mData(land, loadFlags) { + mEsm4DefaultLayerInfo.mDiffuseMap = land.mDefaultDiffuseMap; + mEsm4DefaultLayerInfo.mNormalMap = land.mDefaultNormalMap; } LandObject::LandObject(const ESM::Land& land, int loadFlags) @@ -385,9 +389,105 @@ namespace ESMTerrain return Misc::ResourceHelpers::correctTexturePath(texture, mVFS); } + void Storage::getEsm4Blendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, + std::vector& layerList, ESM::RefId worldspace) + { + const osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize - 1, chunkSize + 1) * 0.5f; + const int startCellX = static_cast(std::floor(origin.x())); + const int startCellY = static_cast(std::floor(origin.y())); + + constexpr int quadsPerCell = 2; + constexpr int quadSize = ESM4::Land::sVertsPerSide / quadsPerCell; + const int quadCount = static_cast(chunkSize * quadsPerCell); + assert(quadCount > 0); + + const int blendmapSize = quadCount * quadSize + 1; + + LandCache cache(startCellX - 1, startCellY - 1, static_cast(std::ceil(chunkSize)) + 2); + std::pair lastCell{ startCellX, startCellY }; + const LandObject* land = getLand(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace), cache); + + std::map textureIndicesMap; + + auto getOrCreateBlendmap = [&](ESM::FormId texId) -> unsigned char* { + auto found = textureIndicesMap.find(texId); + if (found != textureIndicesMap.end()) + return blendmaps[found->second]->data(); + Terrain::LayerInfo info + = texId.isZeroOrUnset() ? land->getEsm4DefaultLayerInfo() : getLandTextureLayerInfo(texId); + osg::ref_ptr image(new osg::Image); + image->allocateImage(blendmapSize, blendmapSize, 1, GL_ALPHA, GL_UNSIGNED_BYTE); + std::memset(image->data(), 0, image->getTotalDataSize()); + textureIndicesMap.emplace(texId, blendmaps.size()); + blendmaps.push_back(std::move(image)); + layerList.push_back(std::move(info)); + return blendmaps.back()->data(); + }; + + const auto handleSample = [&](const CellSample& sample) { + const std::pair cell{ sample.mCellX, sample.mCellY }; + if (lastCell != cell) + { + land = getLand(ESM::ExteriorCellLocation(sample.mCellX, sample.mCellY, worldspace), cache); + lastCell = cell; + } + if (!land) + return; + const ESM::LandData* ldata = land->getData(0); + if (!ldata) + return; + int quad; + if (sample.mSrcRow == 0) + quad = sample.mSrcCol == 0 ? 0 : 2; + else + quad = sample.mSrcCol == 0 ? 1 : 3; + const ESM4::Land::Texture& ltex = ldata->getEsm4Texture(quad); + + unsigned char* const baseBlendmap = getOrCreateBlendmap(ESM::FormId::fromUint32(ltex.base.formId)); + int starty = (static_cast(sample.mDstCol) - 1) * quadSize; + int startx = sample.mDstRow * quadSize; + for (int y = std::max(0, starty + 1); y <= starty + quadSize && y < blendmapSize; ++y) + { + unsigned char* const row = baseBlendmap + (blendmapSize - y - 1) * blendmapSize; + for (int x = startx; x < startx + quadSize && x < blendmapSize; ++x) + row[x] = 255; + } + + for (const auto& layer : ltex.layers) + { + unsigned char* const layerBlendmap = getOrCreateBlendmap(ESM::FormId::fromUint32(layer.texture.formId)); + for (const ESM4::Land::VTXT& v : layer.data) + { + int y = v.position / (quadSize + 1); + int x = v.position % (quadSize + 1); + if (x == quadSize || startx + x >= blendmapSize || y == 0 || starty + y >= blendmapSize + || starty + y < 0) + { + continue; + } + int index = (blendmapSize - starty - y - 1) * blendmapSize + startx + x; + int delta = std::clamp(static_cast(v.opacity * 255), 0, 255); + baseBlendmap[index] = std::max(0, baseBlendmap[index] - delta); + layerBlendmap[index] = delta; + } + } + }; + + sampleBlendmaps(chunkSize, origin.x(), origin.y(), quadsPerCell, handleSample); + + if (blendmaps.size() == 1) + blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend + } + void Storage::getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, std::vector& layerList, ESM::RefId worldspace) { + if (ESM::isEsm4Ext(worldspace)) + { + getEsm4Blendmaps(chunkSize, chunkCenter, blendmaps, layerList, worldspace); + return; + } + const osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize, chunkSize) * 0.5f; const int startCellX = static_cast(std::floor(origin.x())); const int startCellY = static_cast(std::floor(origin.y())); @@ -613,6 +713,47 @@ namespace ESMTerrain return info; } + Terrain::LayerInfo Storage::getLandTextureLayerInfo(ESM::FormId id) + { + if (const ESM4::LandTexture* ltex = getEsm4LandTexture(id)) + { + if (!ltex->mTextureFile.empty()) + return getLayerInfo("textures/landscape/" + ltex->mTextureFile); // TES4 + if (const ESM4::TextureSet* txst = getEsm4TextureSet(ltex->mTexture)) + return getTextureSetLayerInfo(*txst); // TES5 + else + Log(Debug::Warning) << "TextureSet not found: " << ltex->mTexture.toString(); + } + else + Log(Debug::Warning) << "LandTexture not found: " << id.toString(); + return getLayerInfo(""); + } + + Terrain::LayerInfo Storage::getTextureSetLayerInfo(const ESM4::TextureSet& txst) + { + Terrain::LayerInfo info; + + assert(!txst.mDiffuse.empty() && "getlayerInfo: empty diffuse map"); + info.mDiffuseMap = "textures/" + txst.mDiffuse; + + if (!txst.mNormalMap.empty()) + info.mNormalMap = "textures/" + txst.mNormalMap; + + // FIXME: this flag indicates height info in alpha channel of normal map + // but the normal map alpha channel has specular info instead + // (probably needs some flag in the terrain shader to fix) + info.mParallax = false; + // FIXME: this flag indicates specular info in alpha channel of diffuse + // but the diffuse alpha channel has transparency data instead + // (probably needs some flag in the terrain shader to fix) + info.mSpecular = false; + + // FIXME: should support other features of ESM4::TextureSet + // probably need corresponding support in the terrain shader + + return info; + } + float Storage::getCellWorldSize(ESM::RefId worldspace) { return static_cast(ESM::getCellSize(worldspace)); @@ -623,9 +764,12 @@ namespace ESMTerrain return ESM::getLandSize(worldspace); } - int Storage::getBlendmapScale(float chunkSize) + int Storage::getTextureTileCount(float chunkSize, ESM::RefId worldspace) { - return ESM::Land::LAND_TEXTURE_SIZE * chunkSize; + if (ESM::isEsm4Ext(worldspace)) + return static_cast(2 * ESM4::Land::sQuadTexturePerSide * chunkSize); + else + return static_cast(ESM::Land::LAND_TEXTURE_SIZE * chunkSize); } } diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 402f2147ab..be3ba87751 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -13,6 +14,8 @@ namespace ESM4 { struct Land; + struct LandTexture; + struct TextureSet; } namespace ESM @@ -51,9 +54,13 @@ namespace ESMTerrain int getPlugin() const { return mData.getPlugin(); } + const Terrain::LayerInfo& getEsm4DefaultLayerInfo() const { return mEsm4DefaultLayerInfo; } + private: ESM::LandData mData; + Terrain::LayerInfo mEsm4DefaultLayerInfo; + LandObject(const LandObject& copy, const osg::CopyOp& copyOp); }; @@ -74,6 +81,11 @@ namespace ESMTerrain // Not implemented in this class, because we need different Store implementations for game and editor virtual osg::ref_ptr getLand(ESM::ExteriorCellLocation cellLocation) = 0; virtual const std::string* getLandTexture(std::uint16_t index, int plugin) = 0; + + // Not implemented in this class because requires ESMStore + virtual const ESM4::LandTexture* getEsm4LandTexture(ESM::RefId ltexId) const { return nullptr; } + virtual const ESM4::TextureSet* getEsm4TextureSet(ESM::RefId txstId) const { return nullptr; } + /// Get bounds of the whole terrain in cell units void getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) override = 0; @@ -120,7 +132,7 @@ namespace ESMTerrain /// Get the number of vertices on one side for each cell. Should be (power of two)+1 int getCellVertices(ESM::RefId worldspace) override; - int getBlendmapScale(float chunkSize) override; + int getTextureTileCount(float chunkSize, ESM::RefId worldspace) override; float getVertexHeight(const ESM::LandData* data, int x, int y) { @@ -159,6 +171,11 @@ namespace ESMTerrain bool mAutoUseSpecularMaps; Terrain::LayerInfo getLayerInfo(const std::string& texture); + Terrain::LayerInfo getTextureSetLayerInfo(const ESM4::TextureSet& txst); + Terrain::LayerInfo getLandTextureLayerInfo(ESM::FormId id); + + void getEsm4Blendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, + std::vector& layerList, ESM::RefId worldspace); }; } diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 49fdd996a7..bef98b73f0 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -1,6 +1,7 @@ #include "configurationmanager.hpp" #include +#include #include #include diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 184c6ebb82..da4397146b 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -1,7 +1,6 @@ #ifndef COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP -#include #include #include #include diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index 5865298fe5..a88a1a62bb 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -528,8 +528,6 @@ namespace Fx { return parseBool(); } - - error(Misc::StringUtils::format("failed setting uniform type")); } template @@ -557,10 +555,12 @@ namespace Fx { if constexpr (std::is_same_v) error("bool arrays currently unsupported"); - - int size = parseInteger(); - if (size > 1) - data.mArray = std::vector(size); + else + { + int size = parseInteger(); + if (size > 1) + data.mArray = std::vector(size); + } } else if (key == "min") { diff --git a/components/fx/types.hpp b/components/fx/types.hpp index ddfa966010..440bc69470 100644 --- a/components/fx/types.hpp +++ b/components/fx/types.hpp @@ -220,10 +220,11 @@ namespace Fx return osg::Uniform::FLOAT; else if constexpr (std::is_same_v) return osg::Uniform::INT; - else if constexpr (std::is_same_v) + else + { + static_assert(std::is_same_v, "Non-exhaustive visitor"); return osg::Uniform::BOOL; - - return std::nullopt; + } }, mData); } @@ -293,15 +294,14 @@ namespace Fx return Misc::StringUtils::format("const int %s=%i;", mName, value); } - else if constexpr (std::is_same_v) + else { + static_assert(std::is_same_v, "Non-exhaustive visitor"); if (useUniform) return Misc::StringUtils::format("uniform bool %s;", uname); return Misc::StringUtils::format("const bool %s=%s;", mName, value ? "true" : "false"); } - - return std::nullopt; }, mData); } diff --git a/components/l10n/manager.cpp b/components/l10n/manager.cpp index 27a4d603d4..1f75c5b073 100644 --- a/components/l10n/manager.cpp +++ b/components/l10n/manager.cpp @@ -14,7 +14,7 @@ namespace L10n mPreferredLocales.clear(); if (gmstHasPriority) mPreferredLocales.push_back(icu::Locale("gmst")); - std::set langSet; + std::set> langSet; for (const auto& lang : langs) { if (langSet.contains(lang)) @@ -31,10 +31,10 @@ namespace L10n msg << " " << l.getName(); } for (auto& [key, context] : mCache) - updateContext(key.first, *context); + updateContext(std::get<0>(key), *context); } - void Manager::readLangData(const std::string& name, MessageBundles& ctx, const icu::Locale& lang) + void Manager::readLangData(std::string_view name, MessageBundles& ctx, const icu::Locale& lang) { std::string langName(lang.getName()); langName += ".yaml"; @@ -58,7 +58,7 @@ namespace L10n } } - void Manager::updateContext(const std::string& name, MessageBundles& ctx) + void Manager::updateContext(std::string_view name, MessageBundles& ctx) { icu::Locale fallbackLocale = ctx.getFallbackLocale(); ctx.setPreferredLocales(mPreferredLocales); @@ -89,9 +89,9 @@ namespace L10n } std::shared_ptr Manager::getContext( - const std::string& contextName, const std::string& fallbackLocaleName) + std::string_view contextName, const std::string& fallbackLocaleName) { - std::pair key(contextName, fallbackLocaleName); + std::tuple key(contextName, fallbackLocaleName); auto it = mCache.find(key); if (it != mCache.end()) return it->second; @@ -102,7 +102,7 @@ namespace L10n for (char c : contextName) valid = valid && allowedChar(c); if (!valid) - throw std::runtime_error(std::string("Invalid l10n context name: ") + contextName); + throw std::runtime_error("Invalid l10n context name: " + std::string(contextName)); icu::Locale fallbackLocale(fallbackLocaleName.c_str()); std::shared_ptr ctx = std::make_shared(mPreferredLocales, fallbackLocale); ctx->setGmstLoader(mGmstLoader); diff --git a/components/l10n/manager.hpp b/components/l10n/manager.hpp index 4b047fa9d7..89a9bd4b05 100644 --- a/components/l10n/manager.hpp +++ b/components/l10n/manager.hpp @@ -27,20 +27,20 @@ namespace L10n void setGmstLoader(std::function fn) { mGmstLoader = std::move(fn); } std::shared_ptr getContext( - const std::string& contextName, const std::string& fallbackLocale = "en"); + std::string_view contextName, const std::string& fallbackLocale = "en"); - std::string getMessage(const std::string& contextName, std::string_view key) + std::string getMessage(std::string_view contextName, std::string_view key) { return getContext(contextName)->formatMessage(key, {}, {}); } private: - void readLangData(const std::string& name, MessageBundles& ctx, const icu::Locale& lang); - void updateContext(const std::string& name, MessageBundles& ctx); + void readLangData(std::string_view name, MessageBundles& ctx, const icu::Locale& lang); + void updateContext(std::string_view name, MessageBundles& ctx); const VFS::Manager* mVFS; std::vector mPreferredLocales; - std::map, std::shared_ptr> mCache; + std::map, std::shared_ptr, std::less<>> mCache; std::function mGmstLoader; }; diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp index e8a56a9bb3..665704e30d 100644 --- a/components/l10n/messagebundles.cpp +++ b/components/l10n/messagebundles.cpp @@ -9,6 +9,50 @@ namespace L10n { + namespace + { + std::string getErrorText(const UParseError& parseError) + { + icu::UnicodeString preContext(parseError.preContext), postContext(parseError.postContext); + std::string parseErrorString; + preContext.toUTF8String(parseErrorString); + postContext.toUTF8String(parseErrorString); + return parseErrorString; + } + + template + bool checkSuccess(const icu::ErrorCode& status, const UParseError& parseError, Args const&... message) + { + if (status.isFailure()) + { + std::string errorText = getErrorText(parseError); + if (!errorText.empty()) + { + (Log(Debug::Error) << ... << message) + << ": " << status.errorName() << " in \"" << errorText << "\""; + } + else + { + (Log(Debug::Error) << ... << message) << ": " << status.errorName(); + } + } + return status.isSuccess(); + } + + std::string loadGmst( + const std::function& gmstLoader, const icu::MessageFormat* message) + { + icu::UnicodeString gmstNameUnicode; + std::string gmstName; + icu::ErrorCode success; + message->format(nullptr, nullptr, 0, gmstNameUnicode, success); + gmstNameUnicode.toUTF8String(gmstName); + if (gmstLoader) + return gmstLoader(gmstName); + return "GMST:" + gmstName; + } + } + MessageBundles::MessageBundles(const std::vector& preferredLocales, icu::Locale& fallbackLocale) : mFallbackLocale(fallbackLocale) { @@ -39,33 +83,6 @@ namespace L10n } } - std::string getErrorText(const UParseError& parseError) - { - icu::UnicodeString preContext(parseError.preContext), postContext(parseError.postContext); - std::string parseErrorString; - preContext.toUTF8String(parseErrorString); - postContext.toUTF8String(parseErrorString); - return parseErrorString; - } - - static bool checkSuccess( - const icu::ErrorCode& status, const std::string& message, const UParseError parseError = UParseError()) - { - if (status.isFailure()) - { - std::string errorText = getErrorText(parseError); - if (!errorText.empty()) - { - Log(Debug::Error) << message << ": " << status.errorName() << " in \"" << errorText << "\""; - } - else - { - Log(Debug::Error) << message << ": " << status.errorName(); - } - } - return status.isSuccess(); - } - void MessageBundles::load(std::istream& input, const icu::Locale& lang) { YAML::Node data = YAML::Load(input); @@ -80,20 +97,19 @@ namespace L10n icu::ErrorCode status; UParseError parseError; icu::MessageFormat message(pattern, langOrEn, parseError, status); - if (checkSuccess(status, std::string("Failed to create message ") + key + " for locale " + lang.getName(), - parseError)) + if (checkSuccess(status, parseError, "Failed to create message ", key, " for locale ", lang.getName())) { - mBundles[localeName].insert(std::make_pair(key, message)); + mBundles[localeName].emplace(key, message); } } } - const icu::MessageFormat* MessageBundles::findMessage(std::string_view key, const std::string& localeName) const + const icu::MessageFormat* MessageBundles::findMessage(std::string_view key, std::string_view localeName) const { auto iter = mBundles.find(localeName); if (iter != mBundles.end()) { - auto message = iter->second.find(key.data()); + auto message = iter->second.find(key); if (message != iter->second.end()) { return &(message->second); @@ -116,20 +132,6 @@ namespace L10n return formatMessage(key, argNames, argValues); } - static std::string loadGmst( - const std::function gmstLoader, const icu::MessageFormat* message) - { - icu::UnicodeString gmstNameUnicode; - std::string gmstName; - icu::ErrorCode success; - message->format(nullptr, nullptr, 0, gmstNameUnicode, success); - gmstNameUnicode.toUTF8String(gmstName); - if (gmstLoader) - return gmstLoader(gmstName); - else - return "GMST:" + gmstName; - } - std::string MessageBundles::formatMessage(std::string_view key, const std::vector& argNames, const std::vector& args) const { @@ -158,7 +160,7 @@ namespace L10n message->format(argNames.data(), args.data(), static_cast(args.size()), result, success); else message->format(nullptr, nullptr, static_cast(args.size()), result, success); - checkSuccess(success, std::string("Failed to format message ") + key.data()); + checkSuccess(success, {}, "Failed to format message ", key); result.toUTF8String(resultString); return resultString; } @@ -171,7 +173,7 @@ namespace L10n icu::MessageFormat defaultMessage( icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), static_cast(key.size()))), defaultLocale, parseError, success); - if (!checkSuccess(success, std::string("Failed to create message ") + key.data(), parseError)) + if (!checkSuccess(success, parseError, "Failed to create message ", key)) // If we can't parse the key as a pattern, just return the key return std::string(key); @@ -180,7 +182,7 @@ namespace L10n argNames.data(), args.data(), static_cast(args.size()), result, success); else defaultMessage.format(nullptr, nullptr, static_cast(args.size()), result, success); - checkSuccess(success, std::string("Failed to format message ") + key.data()); + checkSuccess(success, {}, "Failed to format message ", key); result.toUTF8String(resultString); return resultString; } diff --git a/components/l10n/messagebundles.hpp b/components/l10n/messagebundles.hpp index f142d3f2ac..15ccbaa630 100644 --- a/components/l10n/messagebundles.hpp +++ b/components/l10n/messagebundles.hpp @@ -10,6 +10,8 @@ #include #include +#include + namespace L10n { /** @@ -41,18 +43,23 @@ namespace L10n void setPreferredLocales(const std::vector& preferredLocales); const std::vector& getPreferredLocales() const { return mPreferredLocales; } void load(std::istream& input, const icu::Locale& lang); - bool isLoaded(const icu::Locale& loc) const { return mBundles.find(loc.getName()) != mBundles.end(); } + bool isLoaded(const icu::Locale& loc) const + { + return mBundles.find(std::string_view(loc.getName())) != mBundles.end(); + } const icu::Locale& getFallbackLocale() const { return mFallbackLocale; } void setGmstLoader(std::function fn) { mGmstLoader = std::move(fn); } private: + template + using StringMap = std::unordered_map>; // icu::Locale isn't hashable (or comparable), so we use the string form instead, which is canonicalized - std::unordered_map> mBundles; + StringMap> mBundles; const icu::Locale mFallbackLocale; std::vector mPreferredLocaleStrings; std::vector mPreferredLocales; std::function mGmstLoader; - const icu::MessageFormat* findMessage(std::string_view key, const std::string& localeName) const; + const icu::MessageFormat* findMessage(std::string_view key, std::string_view localeName) const; }; } diff --git a/components/lua/l10n.cpp b/components/lua/l10n.cpp index 8153efd5b5..856ab8a808 100644 --- a/components/lua/l10n.cpp +++ b/components/lua/l10n.cpp @@ -66,7 +66,7 @@ namespace LuaUtil }; return sol::make_object( - lua, [manager](const std::string& contextName, sol::optional fallbackLocale) { + lua, [manager](std::string_view contextName, sol::optional fallbackLocale) { if (fallbackLocale) return L10nContext{ manager->getContext(contextName, *fallbackLocale) }; else diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 5eff211894..cdeb528205 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -215,12 +215,13 @@ namespace LuaUtil return true; } } + return false; } - else if constexpr (std::is_same_v) + else { + static_assert(std::is_same_v, "Non-exhaustive visitor"); return variant.mScripts.count(scriptId) != 0; } - return false; }, mData); } diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 18f72104bd..681bc0607a 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -4,6 +4,7 @@ #include "lower.hpp" #include +#include #include #include #include @@ -85,17 +86,17 @@ namespace Misc::StringUtils { using is_transparent = void; - constexpr std::size_t operator()(std::string_view str) const + std::size_t operator()(std::string_view str) const { // FNV-1a - std::size_t hash{ 0xcbf29ce484222325ull }; - constexpr std::size_t prime{ 0x00000100000001B3ull }; + std::uint64_t hash{ 0xcbf29ce484222325ull }; + constexpr std::uint64_t prime{ 0x00000100000001B3ull }; for (char c : str) { - hash ^= static_cast(toLower(c)); + hash ^= static_cast(toLower(c)); hash *= prime; } - return hash; + return std::hash()(hash); } }; diff --git a/components/sceneutil/depth.hpp b/components/sceneutil/depth.hpp index a9f99b145f..b9004c457f 100644 --- a/components/sceneutil/depth.hpp +++ b/components/sceneutil/depth.hpp @@ -95,13 +95,10 @@ namespace SceneUtil static void setReversed(bool reverseZ) { - static bool init = false; - - if (!init) - { + [[maybe_unused]] static const bool init = [&] { AutoDepth::sReversed = reverseZ; - init = true; - } + return true; + }(); } static bool isReversed() diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 9fa01385d5..d456795781 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -27,7 +27,6 @@ #include #include -#include #include #include "glextensions.hpp" diff --git a/components/settings/categories/sound.hpp b/components/settings/categories/sound.hpp index 8398a38c55..99e70fcdb2 100644 --- a/components/settings/categories/sound.hpp +++ b/components/settings/categories/sound.hpp @@ -24,6 +24,7 @@ namespace Settings SettingValue mHrtfEnable{ mIndex, "Sound", "hrtf enable" }; SettingValue mHrtf{ mIndex, "Sound", "hrtf" }; SettingValue mCameraListener{ mIndex, "Sound", "camera listener" }; + SettingValue mDopplerFactor{ mIndex, "Sound", "doppler factor", makeClampSanitizerFloat(0, 1) }; }; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 3d7c2b1dfc..242f3e5700 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -5,6 +5,7 @@ #include +#include #include #include @@ -205,10 +206,10 @@ namespace Terrain blendmapTextures.push_back(texture); } - float blendmapScale = mStorage->getBlendmapScale(chunkSize); + float tileCount = mStorage->getTextureTileCount(chunkSize, mWorldspace); return ::Terrain::createPasses( - useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale); + useShaders, mSceneManager, layers, blendmapTextures, tileCount, tileCount, ESM::isEsm4Ext(mWorldspace)); } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f& chunkCenter, unsigned char lod, diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 9c3a7f589d..350b174d69 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -224,7 +224,7 @@ namespace Terrain { std::vector> createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector& layers, const std::vector>& blendmaps, - int blendmapScale, float layerTileSize) + int blendmapScale, float layerTileSize, bool esm4terrain) { auto& shaderManager = sceneManager->getShaderManager(); std::vector> passes; @@ -269,7 +269,8 @@ namespace Terrain osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); stateset->setTextureAttributeAndModes(1, blendmap.get()); - stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); + if (!esm4terrain) + stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); stateset->addUniform(UniformCollection::value().mBlendMap); } @@ -329,7 +330,8 @@ namespace Terrain stateset->setTextureAttributeAndModes(1, blendmap.get()); // This is to map corner vertices directly to the center of a blendmap texel. - stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); + if (!esm4terrain) + stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); stateset->setTextureAttributeAndModes(1, TexEnvCombine::value(), osg::StateAttribute::ON); } } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 1dbf6d8fc8..fca14d8b3e 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -20,14 +20,13 @@ namespace Terrain { osg::ref_ptr mDiffuseMap; osg::ref_ptr mNormalMap; // optional - bool mParallax; - bool mSpecular; + bool mParallax = false; + bool mSpecular = false; }; std::vector> createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector& layers, const std::vector>& blendmaps, - int blendmapScale, float layerTileSize); - + int blendmapScale, float layerTileSize, bool esm4terrain = false); } #endif diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 99938d7b3a..0d7f1b7762 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -289,11 +289,12 @@ namespace Terrain , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) - , mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 4.f : 1 / 8.f) + , mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 2.f : 1 / 8.f) , mDebugTerrainChunks(debugChunks) { mChunkManager->setCompositeMapSize(compMapResolution); - mChunkManager->setCompositeMapLevel(compMapLevel); + mChunkManager->setCompositeMapLevel( + ESM::isEsm4Ext(worldspace) ? compMapLevel * 2 /*because cells are twice smaller*/ : compMapLevel); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); mChunkManagers.push_back(mChunkManager.get()); diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index b1007c13e6..38aee20b8b 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -88,7 +88,8 @@ namespace Terrain /// Get the number of vertices on one side for each cell. Should be (power of two)+1 virtual int getCellVertices(ESM::RefId worldspace) = 0; - virtual int getBlendmapScale(float chunkSize) = 0; + /// Get the number of texture tiles on one side per chunk (chunkSize 1.0 = 1 cell). + virtual int getTextureTileCount(float chunkSize, ESM::RefId worldspace) = 0; }; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 88fcaf667d..aff72d2d79 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -9,6 +9,8 @@ #include "heightcull.hpp" #include "storage.hpp" #include "view.hpp" + +#include #include namespace Terrain @@ -27,13 +29,13 @@ namespace Terrain unsigned int borderMask) : Terrain::World( parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask, worldspace, expiryDelay) - , mNumSplits(4) + , mNumSplits(ESM::isEsm4Ext(worldspace) ? 2 : 4) { } TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, ESM::RefId worldspace, unsigned int nodeMask) : Terrain::World(parent, storage, nodeMask, worldspace) - , mNumSplits(4) + , mNumSplits(ESM::isEsm4Ext(worldspace) ? 2 : 4) { } diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 29b14aee55..cc1e45403f 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -28,7 +28,7 @@ Engine handler is a function defined by a script, that can be called by the engi | `assigned to a script in openmw-cs (not yet implemented).` | ``onInterfaceOverride`` can be called before ``onInit``. * - onUpdate(dt) - - | Called every frame if the game is not paused. `dt` is + - | Called every frame in the Lua thread (even if the game is paused). `dt` is | the simulation time from the last update in seconds. * - onSave() -> savedData - | Called when the game is saving. May be called in inactive state, diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 590ff14d88..810739caeb 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -107,8 +107,8 @@ Example: local attack = { attacker = self, weapon = Actor.getEquipment(self, Actor.EQUIPMENT_SLOT.CarriedRight), - sourceType = I.Combat.ATTACK_SOURCE_TYPE.Melee, - strenght = 1, + sourceType = I.Combat.ATTACK_SOURCE_TYPES.Melee, + strength = 1, type = self.ATTACK_TYPE.Chop, damage = { health = 20, diff --git a/docs/source/reference/lua-scripting/index_interfaces.rst b/docs/source/reference/lua-scripting/index_interfaces.rst index 37a2df63c4..0b113c7744 100644 --- a/docs/source/reference/lua-scripting/index_interfaces.rst +++ b/docs/source/reference/lua-scripting/index_interfaces.rst @@ -11,6 +11,7 @@ Interfaces AI AnimationController Camera + Combat Controls Crimes GamepadControls diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index e38cd9de78..4e73df989c 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -121,3 +121,13 @@ Sound Settings When true, uses the camera position and direction for audio instead of the player position. This makes audio in third person sound relative to camera instead of the player. False is vanilla Morrowind behaviour. + +.. omw-setting:: + :title: doppler factor + :type: float32 + :range: 0.0 (disabled), 1.0 (maximum strength) + :default: 0.25 + :location: :bdg-success:`Launcher > Settings > Audio` + + This setting controls the strength of the Doppler effect. The Doppler effect increases or decreases the pitch of sounds + relative to the velocity of the sound source and the listener. diff --git a/files/data/mygui/openmw_spellcreation_dialog.layout b/files/data/mygui/openmw_spellcreation_dialog.layout index c284927a37..d616e00dcb 100644 --- a/files/data/mygui/openmw_spellcreation_dialog.layout +++ b/files/data/mygui/openmw_spellcreation_dialog.layout @@ -68,6 +68,12 @@ + + + + + + diff --git a/files/data/mygui/openmw_trainingwindow.layout b/files/data/mygui/openmw_trainingwindow.layout index 80526068ce..f3cadcaf46 100644 --- a/files/data/mygui/openmw_trainingwindow.layout +++ b/files/data/mygui/openmw_trainingwindow.layout @@ -1,28 +1,28 @@ - + - + - + - + - + - + - + diff --git a/files/data/scripts/omw/camera/camera.lua b/files/data/scripts/omw/camera/camera.lua index 6730e0c069..52b64906ea 100644 --- a/files/data/scripts/omw/camera/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -182,6 +182,10 @@ local function updateCrosshair() end local function onUpdate(dt) + if dt <= 0 then + return + end + camera.setExtraPitch(0) camera.setExtraYaw(0) camera.setExtraRoll(0) diff --git a/files/data/scripts/omw/mechanics/animationcontroller.lua b/files/data/scripts/omw/mechanics/animationcontroller.lua index 91cb60d177..9edc7565ca 100644 --- a/files/data/scripts/omw/mechanics/animationcontroller.lua +++ b/files/data/scripts/omw/mechanics/animationcontroller.lua @@ -41,6 +41,10 @@ end local initialized = false local function onUpdate(dt) + if dt <= 0 then + return + end + -- The script is loaded before the actor's CharacterController object is initialized, therefore -- we have to delay this initialization step or the call won't have any effect. if not initialized then diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 8b4d618917..a0d1b11362 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -39,7 +39,8 @@ end local function skillLevelUpHandler(skillid, source, params) local skillStat = NPC.stats.skills[skillid](self) - if skillStat.base >= 100 then + if (skillStat.base >= 100 and params.skillIncreaseValue > 0) or + (skillStat.base <= 0 and params.skillIncreaseValue < 0) then return false end @@ -62,25 +63,67 @@ local function skillLevelUpHandler(skillid, source, params) = levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + params.levelUpSpecializationIncreaseValue; end - local skillRecord = Skill.record(skillid) - local npcRecord = NPC.record(self) - local class = NPC.classes.record(npcRecord.class) + if source ~= 'jail' then + local skillRecord = Skill.record(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) - ambient.playSound("skillraise") + ambient.playSound("skillraise") - local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base) + local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base) - if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then - message = '#{sBookSkillMessage}\n'..message + if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then + message = '#{sBookSkillMessage}\n'..message + end + + ui.showMessage(message, { showInDialogue = false }) + + if levelStat.progress >= core.getGMST('iLevelUpTotal') then + ui.showMessage('#{sLevelUpMsg}', { showInDialogue = false }) + end + + if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end end +end - ui.showMessage(message, { showInDialogue = false }) +local function jailTimeServed(days) + if not days or days <= 0 then + return + end - if levelStat.progress >= core.getGMST('iLevelUpTotal') then - ui.showMessage('#{sLevelUpMsg}', { showInDialogue = false }) + local oldSkillLevels = {} + local skillByNumber = {} + for skillid, skillStat in pairs(NPC.stats.skills) do + oldSkillLevels[skillid] = skillStat(self).base + skillByNumber[#skillByNumber+1] = skillid + end + + math.randomseed(core.getSimulationTime()) + for day=1,days do + local skillid = skillByNumber[math.random(#skillByNumber)] + -- skillLevelUp() handles skill-based increase/decrease + I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Jail) end - if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end + local message = '' + if days == 1 then + message = string.format(core.getGMST('sNotifyMessage42'), days) + else + message = string.format(core.getGMST('sNotifyMessage43'), days) + end + for skillid, skillStat in pairs(NPC.stats.skills) do + local diff = skillStat(self).base - oldSkillLevels[skillid] + if diff ~= 0 then + local skillMsg = core.getGMST('sNotifyMessage39') + if diff < 0 then + skillMsg = core.getGMST('sNotifyMessage44') + end + local skillRecord = Skill.record(skillid) + message = message..'\n'..string.format(skillMsg, skillRecord.name, skillStat(self).base) + end + end + + I.UI.showInteractiveMessage(message) end local function skillUsedHandler(skillid, params) @@ -96,7 +139,11 @@ local function skillUsedHandler(skillid, params) end end -local function onUpdate() +local function onUpdate(dt) + if dt <= 0 then + return + end + if self.cell ~= cell then cell = self.cell onCellChange() @@ -110,6 +157,7 @@ I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) return { engineHandlers = { onUpdate = onUpdate, + _onJailTimeServed = jailTimeServed, }, eventHandlers = { diff --git a/files/data/scripts/omw/music/actor.lua b/files/data/scripts/omw/music/actor.lua index 8f3ac7915a..02cded7904 100755 --- a/files/data/scripts/omw/music/actor.lua +++ b/files/data/scripts/omw/music/actor.lua @@ -11,7 +11,7 @@ local function emitTargetsChanged() end end -local function onUpdate() +local function onUpdate(dt) if types.Actor.isDeathFinished(self) or not types.Actor.isInActorsProcessingRange(self) then if next(targets) ~= nil then targets = {} @@ -21,10 +21,10 @@ local function onUpdate() return end - -- Early-out for actors without targets and without combat state + -- Early-out for actors without targets and without combat state when the game is not paused -- TODO: use events or engine handlers to detect when targets change local isStanceNothing = types.Actor.getStance(self) == types.Actor.STANCE.Nothing - if isStanceNothing and next(targets) == nil and not AI.isFleeing() then + if isStanceNothing and next(targets) == nil and not AI.isFleeing() and dt > 0 then return end diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index d3a224dc46..9b58d81174 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -38,6 +38,7 @@ local Skill = core.stats.Skill -- Table of all existing sources for skill increases. Any sources not listed below will be treated as equal to Trainer. -- @type SkillLevelUpSource -- @field #string Book book +-- @field #string Jail jail -- @field #string Trainer trainer -- @field #string Usage usage @@ -131,15 +132,17 @@ local function skillLevelUp(skillid, source) levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute') end - local options = - { - skillIncreaseValue = 1, - levelUpProgress = levelUpProgress, - levelUpAttribute = skillRecord.attribute, - levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue, - levelUpSpecialization = skillRecord.specialization, - levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization'), - } + local options = {} + if source == 'jail' and not (skillid == 'security' or skillid == 'sneak') then + options.skillIncreaseValue = -1 + else + options.skillIncreaseValue = 1 + options.levelUpProgress = levelUpProgress + options.levelUpAttribute = skillRecord.attribute + options.levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue + options.levelUpSpecialization = skillRecord.specialization + options.levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization') + end for i = #skillLevelUpHandlers, 1, -1 do if skillLevelUpHandlers[i](skillid, source, options) == false then @@ -156,8 +159,15 @@ return { -- @context local -- @usage local I = require('openmw.interfaces') -- + -- -- Make jail time hurt sneak skill instead of benefitting it + -- I.SkillProgression.addSkillLevelUpHandler(function(skillid, source, options) + -- if skillid == 'sneak' and source == 'jail' and options.skillIncreaseValue > 0 then + -- options.skillIncreaseValue = -options.skillIncreaseValue + -- end + -- end) + -- -- -- Forbid increasing destruction skill past 50 - -- I.SkillProgression.addSkillLevelUpHandler(function(skillid, options) + -- I.SkillProgression.addSkillLevelUpHandler(function(skillid, source, options) -- if skillid == 'destruction' and types.NPC.stats.skills.destruction(self).base >= 50 then -- return false -- end @@ -187,7 +197,7 @@ return { -- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers. -- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: -- - -- * `skillIncreaseValue` - The numeric amount of skill levels gained. + -- * `skillIncreaseValue` - The numeric amount of skill levels gained. By default this is 1, except when the source is jail in which case it will instead be -1 for all skills except sneak and security. -- * `levelUpProgress` - The numeric amount of level up progress gained. -- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up. -- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up. @@ -263,7 +273,7 @@ return { --- Trigger a skill level up, activating relevant handlers -- @function [parent=#SkillProgression] skillLevelUp -- @param #string skillid The id of the skill to level up. - -- @param #SkillLevelUpSource source The source of the skill increase. + -- @param #SkillLevelUpSource source The source of the skill increase. Note that passing a value of @{#SkillLevelUpSource.Jail} will cause a skill decrease for all skills except sneak and security. skillLevelUp = skillLevelUp, --- @{#SkillLevelUpSource} @@ -272,6 +282,7 @@ return { Book = 'book', Usage = 'usage', Trainer = 'trainer', + Jail = 'jail', }, --- Compute the total skill gain required to level up a skill based on its current level, and other modifying factors such as major skills and specialization. diff --git a/files/data/scripts/omw/ui.lua b/files/data/scripts/omw/ui.lua index 31502fd6ee..c63c085fab 100644 --- a/files/data/scripts/omw/ui.lua +++ b/files/data/scripts/omw/ui.lua @@ -171,7 +171,7 @@ return { interface = { --- Interface version -- @field [parent=#UI] #number version - version = 2, + version = 3, --- All available UI modes. -- Use `view(I.UI.MODE)` in `luap` console mode to see the list. @@ -254,6 +254,13 @@ return { -- @return #boolean isWindowVisible = isWindowVisible, + --- + -- Shows a message as an interactive message box pausing the game, with a single button with the localized text OK. + -- @function [parent=#UI] showInteractiveMessage + -- @param #string message Message to display + -- @param #table options Options (none yet) + showInteractiveMessage = ui._showInteractiveMessage + -- TODO -- registerHudElement = function(name, showFn, hideFn) end, -- showHudElement = function(name, bool) end, diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 01a87679eb..d03a2c0550 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -1467,5 +1467,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Show Controller Tooltips By Default + + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> + + + + Doppler Factor + + diff --git a/files/lang/launcher_en.ts b/files/lang/launcher_en.ts index be026550f5..4924a21fb7 100644 --- a/files/lang/launcher_en.ts +++ b/files/lang/launcher_en.ts @@ -1467,5 +1467,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Show Controller Tooltips By Default + + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> + + + + Doppler Factor + + diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 3894e988e3..17b9e458fa 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -1470,5 +1470,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Show Controller Tooltips By Default Afficher les info-bulles du contrôleur par défaut + + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> + + + + Doppler Factor + + diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 7c97bd9064..25528f4769 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1482,5 +1482,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Show Controller Tooltips By Default Подсказки для геймпадов по умолчанию + + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> + <html><head/><body><p>Определяет силу эффекта Доплера. Нулевое значение означает, что эффект отключен полностью.</p><p>Эффект Доплера увеличивает или уменьшает высоту звуков в зависимости от скорости источника звука и слушателя.</p></body></html> + + + Doppler Factor + Множитель эффекта Доплера + diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index 2c5bbb451e..efc66a48fa 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -1486,5 +1486,13 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Show Controller Tooltips By Default Visa handkontrollinforutor som standard + + <html><head/><body><p>Controls the strength of the Doppler effect. Zero means it is completely disabled.</p><p>The Doppler effect increases or decreases the pitch of sounds relative to the velocity of the sound source and the listener.</p></body></html> + <html><head/><body><p>Kontrollerar styrkan på dopplereffekten. Noll innebär helt inaktiverat.</p><p>Dopplereffekten höjer eller sänker tonhöjden på ljud i förhållande till ljudkällans hastighet och lyssnaren.</p></body></html> + + + Doppler Factor + Dopplerfaktor + diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index 436f10de1a..a9e8c08d4a 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -18,7 +18,7 @@ -- * `scale` - a boolean, to set if the sound's pitch should be scaled by simulation time scaling (default: true); -- * `loop` - a boolean, to set if the sound should be repeated when it ends (default: false); -- @usage local params = { --- timeOffset=0.1 +-- timeOffset=0.1, -- volume=0.3, -- scale=false, -- pitch=1.0, @@ -38,7 +38,7 @@ -- * `scale` - a boolean, to set if the sound's pitch should be scaled by simulation time scaling (default: true); -- * `loop` - a boolean, to set if the sound should be repeated when it ends (default: false); -- @usage local params = { --- timeOffset=0.1 +-- timeOffset=0.1, -- volume=0.3, -- scale=false, -- pitch=1.0, diff --git a/files/lua_api/openmw/animation.lua b/files/lua_api/openmw/animation.lua index 929a784ba9..9fcbe8ea21 100644 --- a/files/lua_api/openmw/animation.lua +++ b/files/lua_api/openmw/animation.lua @@ -188,7 +188,7 @@ -- -- * `loops` - a number >= 0, the number of times the animation should loop after the first play (default: 0). -- * `priority` - Either a single #Priority value that will be assigned to all bone groups. Or a table mapping bone groups to its priority (default: PRIORITY.Default). --- * `blendMask` - A mask of which bone groups to include in the animation (Default: BLEND_MASK.All. +-- * `blendMask` - A mask of which bone groups to include in the animation (Default: BLEND_MASK.All). -- * `autoDisable` - If true, the animation will be immediately removed upon finishing, which means information will not be possible to query once completed. (Default: true) -- * `speed` - a floating point number >= 0, the speed at which the animation should play (default: 1) -- * `startKey` - the animation key at which the animation should start (default: "start") @@ -242,7 +242,7 @@ -- model = types.Static.record(mgef.hitStatic).model, -- options = { -- vfxId = mgef.id, --- particuleTextureOverride = mgef.particle, +-- particleTextureOverride = mgef.particle, -- loop = false, -- } -- }) @@ -254,7 +254,7 @@ -- Can only be used on self. -- @function [parent=#animation] removeVfx -- @param openmw.core#GameObject actor --- @param #number vfxId an integer ID that uniquely identifies the VFX to remove +-- @param #string vfxId a string ID that uniquely identifies the VFX to remove --- -- Removes all vfx from the actor. diff --git a/files/lua_api/openmw/camera.lua b/files/lua_api/openmw/camera.lua index b6b605fc10..67b3ec2a8e 100644 --- a/files/lua_api/openmw/camera.lua +++ b/files/lua_api/openmw/camera.lua @@ -109,7 +109,6 @@ --- -- Additional summand for the yaw angle; useful for camera shaking effects. --- Setting extra pitch doesn't block player input. -- Full yaw is `getYaw()+getExtraYaw()`. -- @function [parent=#camera] setExtraYaw -- @param #number value @@ -122,7 +121,7 @@ --- -- Additional summand for the roll angle; useful for camera shaking effects. --- Full yaw is `getRoll()+getExtraRoll()`. +-- Full roll is `getRoll()+getExtraRoll()`. -- @function [parent=#camera] setExtraRoll -- @param #number value @@ -150,7 +149,7 @@ --- -- Set preferred offset between tracked position (see `getTrackedPosition`) and the camera focal point (the center of the screen) in third person mode. -- The offset is a 2d vector (X, Y) where X is horizontal (to the right from the character) and Y component is vertical (upward). --- The real offset can differ from the preferred one during smooth transition of if blocked by an obstacle. +-- The real offset can differ from the preferred one during smooth transition or if blocked by an obstacle. -- Smooth transition happens by default every time when the preferred offset was changed. Use `instantTransition()` to skip the current transition. -- @function [parent=#camera] setFocalPreferredOffset -- @param openmw.util#Vector2 offset diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 0567cec859..c1b888bd43 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -42,7 +42,7 @@ -- @return #number --- --- Whether the world is paused (onUpdate doesn't work when the world is paused). +-- Whether the world is paused. -- @function [parent=#core] isWorldPaused -- @return #boolean @@ -801,7 +801,7 @@ -- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); -- * `loop` - a boolean, to set if sound should be repeated when it ends (default: false); -- @usage local params = { --- timeOffset=0.1 +-- timeOffset=0.1, -- volume=0.3, -- loop=false, -- pitch=1.0 @@ -822,7 +822,7 @@ -- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); -- * `loop` - a boolean, to set if sound should be repeated when it ends (default: false); -- @usage local params = { --- timeOffset=0.1 +-- timeOffset=0.1, -- volume=0.3, -- loop=false, -- pitch=1.0 diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index b2711c895b..6d51987ac7 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1004,7 +1004,7 @@ -- @param openmw.core#GameObject actor NPC object -- @param #string faction Faction ID -- @usage local NPC = require('openmw.types').NPC; --- NPC.clearExpell(player, "mages guild"); +-- NPC.clearExpelled(player, "mages guild"); --- -- Check if NPC is expelled from given faction. diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index ae494f8c99..22126ce8f4 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -119,7 +119,7 @@ -- @param #number ratio --- --- Whether the world is paused (onUpdate doesn't work when the world is paused). +-- Whether the world is paused. -- @function [parent=#world] isWorldPaused -- @return #boolean diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 8957611039..2c4bbab953 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -630,6 +630,9 @@ hrtf = # Specifies whether to use camera as audio listener camera listener = false +# Specifies strength of doppler effect +doppler factor = 0.25 + [Video] # Resolution of the OpenMW window or screen.