diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f81c6f235..97415f1bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,9 @@ Bug #7034: Misc items defined in one content file are not treated as keys if another content file uses them as such Bug #7042: Weapon follow animations that immediately follow the hit animations cause multiple hits Bug #7044: Changing a class' services does not affect autocalculated NPCs + Bug #7054: Quests aren't sorted by name Bug #7084: Resurrecting an actor doesn't take into account base record changes + Bug #7088: Deleting last save game of last character doesn't clear character name/details Feature #6447: Add LOD support to Object Paging Feature #6933: Support high-resolution cursor textures Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData @@ -37,6 +39,7 @@ Feature #6995: Localize the "show effect duration" option Feature #7058: Implement TestModels (T3D) console command Feature #7087: Block resolution change in the Windowed Fullscreen mode + Feature #7130: Ability to set MyGUI logging verbosity 0.48.0 ------ @@ -217,6 +220,7 @@ Feature #5198: Implement "Magic effect expired" event Feature #5454: Clear active spells from actor when he disappears from scene Feature #5489: MCP: Telekinesis fix for activators + Feature #5492: Let rain and snow collide with statics Feature #5701: Convert osgAnimation::RigGeometry to double-buffered custom version Feature #5737: OpenMW-CS: Handle instance move from one cell to another Feature #5928: Allow Glow in the Dahrk to be disabled diff --git a/apps/esmtool/tes4.cpp b/apps/esmtool/tes4.cpp index f72175ec93..1b85c37c11 100644 --- a/apps/esmtool/tes4.cpp +++ b/apps/esmtool/tes4.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -69,6 +70,19 @@ namespace EsmTool template constexpr bool hasFormId = HasFormId::value; + template > + struct HasRefId : std::false_type + { + }; + + template + struct HasRefId> : std::true_type + { + }; + + template + constexpr bool hasRefId = HasRefId::value; + template > struct HasFlags : std::false_type { @@ -169,6 +183,8 @@ namespace EsmTool std::cout << "\n Record: " << ESM::NAME(reader.hdr().record.typeId).toStringView(); if constexpr (hasFormId) std::cout << "\n FormId: " << value.mFormId; + if constexpr (hasRefId) + std::cout << "\n FormId: " << value.mId; if constexpr (hasFlags) std::cout << "\n Record flags: " << recordFlags(value.mFlags); if constexpr (hasEditorId) @@ -182,60 +198,77 @@ namespace EsmTool std::cout << '\n'; } - void readRecord(const Params& params, ESM4::Reader& reader) + bool readRecord(const Params& params, ESM4::Reader& reader) { switch (static_cast(reader.hdr().record.typeId)) { case ESM4::REC_AACT: break; case ESM4::REC_ACHR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ACRE: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ACTI: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ADDN: break; case ESM4::REC_ALCH: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ALOC: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_AMMO: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ANIO: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_APPA: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ARMA: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ARMO: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ARTO: break; case ESM4::REC_ASPC: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_ASTP: break; case ESM4::REC_AVIF: break; case ESM4::REC_BOOK: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_BPTD: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CAMS: break; case ESM4::REC_CCRD: break; case ESM4::REC_CELL: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CLAS: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CLFM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CLMT: break; case ESM4::REC_CLOT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CMNY: break; case ESM4::REC_COBJ: @@ -243,25 +276,30 @@ namespace EsmTool case ESM4::REC_COLL: break; case ESM4::REC_CONT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CPTH: break; case ESM4::REC_CREA: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_CSTY: break; case ESM4::REC_DEBR: break; case ESM4::REC_DIAL: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_DLBR: break; case ESM4::REC_DLVW: break; case ESM4::REC_DOBJ: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_DOOR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_DUAL: break; case ESM4::REC_ECZN: @@ -275,81 +313,103 @@ namespace EsmTool case ESM4::REC_EXPL: break; case ESM4::REC_EYES: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_FACT: break; case ESM4::REC_FLOR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_FLST: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_FSTP: break; case ESM4::REC_FSTS: break; case ESM4::REC_FURN: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_GLOB: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_GMST: break; case ESM4::REC_GRAS: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_GRUP: break; case ESM4::REC_HAIR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_HAZD: break; case ESM4::REC_HDPT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_IDLE: // FIXME: ESM4::IdleAnimation::load does not work with Oblivion.esm - // return readTypedRecord(params, reader); + // readTypedRecord(params, reader); + return true; break; case ESM4::REC_IDLM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_IMAD: break; case ESM4::REC_IMGS: break; case ESM4::REC_IMOD: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_INFO: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_INGR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_IPCT: break; case ESM4::REC_IPDS: break; case ESM4::REC_KEYM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_KYWD: break; case ESM4::REC_LAND: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LCRT: break; case ESM4::REC_LCTN: break; case ESM4::REC_LGTM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LIGH: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LSCR: break; case ESM4::REC_LTEX: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LVLC: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LVLI: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LVLN: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_LVSP: break; case ESM4::REC_MATO: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_MATT: break; case ESM4::REC_MESG: @@ -357,49 +417,66 @@ namespace EsmTool case ESM4::REC_MGEF: break; case ESM4::REC_MISC: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_MOVT: break; case ESM4::REC_MSET: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_MSTT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_MUSC: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_MUST: break; case ESM4::REC_NAVI: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_NAVM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_NOTE: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_NPC_: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_OTFT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_PACK: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_PERK: break; case ESM4::REC_PGRD: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_PGRE: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_PHZD: break; case ESM4::REC_PROJ: break; case ESM4::REC_PWAT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_QUST: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_RACE: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_REFR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_REGN: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_RELA: break; case ESM4::REC_REVB: @@ -407,23 +484,30 @@ namespace EsmTool case ESM4::REC_RFCT: break; case ESM4::REC_ROAD: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SBSP: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SCEN: break; case ESM4::REC_SCOL: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SCPT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SCRL: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SGST: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SHOU: break; case ESM4::REC_SLGM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SMBN: break; case ESM4::REC_SMEN: @@ -433,97 +517,56 @@ namespace EsmTool case ESM4::REC_SNCT: break; case ESM4::REC_SNDR: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SOPM: break; case ESM4::REC_SOUN: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_SPEL: break; case ESM4::REC_SPGD: break; case ESM4::REC_STAT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_TACT: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_TERM: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_TES4: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_TREE: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_TXST: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_VTYP: break; case ESM4::REC_WATR: break; case ESM4::REC_WEAP: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_WOOP: break; case ESM4::REC_WRLD: - return readTypedRecord(params, reader); + readTypedRecord(params, reader); + return true; case ESM4::REC_WTHR: break; } if (!params.mQuite) std::cout << "\n Unsupported record: " << ESM::NAME(reader.hdr().record.typeId).toStringView() << '\n'; - - reader.skipRecordData(); + return false; } - bool readItem(const Params& params, ESM4::Reader& reader); - - bool readGroup(const Params& params, ESM4::Reader& reader) - { - const ESM4::RecordHeader& header = reader.hdr(); - - if (!params.mQuite) - std::cout << "\nGroup: " << toString(static_cast(header.group.type)) << " " - << ESM::NAME(header.group.typeId).toStringView() << '\n'; - - switch (static_cast(header.group.type)) - { - case ESM4::Grp_RecordType: - case ESM4::Grp_InteriorCell: - case ESM4::Grp_InteriorSubCell: - case ESM4::Grp_ExteriorCell: - case ESM4::Grp_ExteriorSubCell: - reader.enterGroup(); - return readItem(params, reader); - case ESM4::Grp_WorldChild: - case ESM4::Grp_CellChild: - case ESM4::Grp_TopicChild: - case ESM4::Grp_CellPersistentChild: - case ESM4::Grp_CellTemporaryChild: - case ESM4::Grp_CellVisibleDistChild: - reader.adjustGRUPFormId(); - reader.enterGroup(); - if (!reader.hasMoreRecs()) - return false; - return readItem(params, reader); - } - - reader.skipGroup(); - - return true; - } - - bool readItem(const Params& params, ESM4::Reader& reader) - { - if (!reader.getRecordHeader() || !reader.hasMoreRecs()) - return false; - - const ESM4::RecordHeader& header = reader.hdr(); - - if (header.record.typeId == ESM4::REC_GRUP) - return readGroup(params, reader); - - readRecord(params, reader); - return true; - } } int loadTes4(const Arguments& info, std::unique_ptr&& stream) @@ -551,12 +594,15 @@ namespace EsmTool } } - while (reader.hasMoreRecs()) - { - reader.exitGroupCheck(); - if (!readItem(params, reader)) - break; - } + auto visitorRec = [¶ms](ESM4::Reader& reader) { return readRecord(params, reader); }; + auto visitorGroup = [¶ms](ESM4::Reader& reader) { + if (params.mQuite) + return; + auto groupType = static_cast(reader.hdr().group.type); + std::cout << "\nGroup: " << toString(groupType) << " " + << ESM::NAME(reader.hdr().group.typeId).toStringView() << '\n'; + }; + ESM4::ReaderUtils::readAll(reader, visitorRec, visitorGroup); } catch (const std::exception& e) { diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index bfd12d27c1..9fe478f94a 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -126,6 +126,7 @@ bool Launcher::AdvancedPage::loadSettings() antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked); } loadSettingBool(adjustCoverageForAlphaTestCheckBox, "adjust coverage for alpha test", "Shaders"); + loadSettingBool(weatherParticleOcclusionCheckBox, "weather particle occlusion", "Shaders"); loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); connect(animSourcesCheckBox, &QCheckBox::toggled, this, &AdvancedPage::slotAnimSourcesToggled); loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); @@ -285,6 +286,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(softParticlesCheckBox, "soft particles", "Shaders"); saveSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders"); saveSettingBool(adjustCoverageForAlphaTestCheckBox, "adjust coverage for alpha test", "Shaders"); + saveSettingBool(weatherParticleOcclusionCheckBox, "weather particle occlusion", "Shaders"); saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index c4288b117c..b00393b8af 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -490,13 +490,7 @@ namespace CSMWorld int Collection::searchId(const ESM::RefId& id) const { - std::map::const_iterator iter - = mIndex.find(Misc::StringUtils::lowerCase(id.getRefIdString())); - - if (iter == mIndex.end()) - return -1; - - return iter->second; + return searchId(id.getRefIdString()); } template diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index da77405d29..51ffe52436 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1242,10 +1242,10 @@ bool CSMWorld::Data::continueLoading(CSMDoc::Messages& messages) case ESM::REC_DIAL: { ESM::Dialogue record; - const std::string& recordIdString = record.mId.getRefIdString(); bool isDeleted = false; record.load(*mReader, isDeleted); + const std::string& recordIdString = record.mId.getRefIdString(); if (isDeleted) { diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index ced1060ce9..e9683cc40a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -23,7 +23,7 @@ add_openmw_dir (mwrender creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover - postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass navmeshmode + postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass navmeshmode precipitationocclusion ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index a9b7abdc7d..464a1acfa0 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -127,10 +127,10 @@ namespace MWClass if (!customData.mSpawn) return; - MWWorld::LiveCellRef* ref = ptr.get(); - + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - const ESM::RefId& id = MWMechanics::getLevelledItem(ref->mBase, true, prng); + const ESM::RefId& id = MWMechanics::getLevelledItem( + store.get().find(ptr.getCellRef().getRefId()), true, prng); if (!id.empty()) { @@ -144,7 +144,6 @@ namespace MWClass customData.mSpawnActorId = -1; } - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); MWWorld::ManualRef manualRef(store, id); manualRef.getPtr().getCellRef().setPosition(ptr.getCellRef().getPosition()); manualRef.getPtr().getCellRef().setScale(ptr.getCellRef().getScale()); diff --git a/apps/openmw/mwgui/debugwindow.cpp b/apps/openmw/mwgui/debugwindow.cpp index fe2fed02bb..2a9ae25664 100644 --- a/apps/openmw/mwgui/debugwindow.cpp +++ b/apps/openmw/mwgui/debugwindow.cpp @@ -104,20 +104,20 @@ namespace MWGui // - Shader editor MyGUI::TabItem* itemLV = mTabControl->addItem("Log Viewer"); - itemLV->setCaptionWithReplacing(" #{DebugMenu:LogViewer} "); + itemLV->setCaptionWithReplacing(" #{OMWEngine:LogViewer} "); mLogView = itemLV->createWidgetReal("LogEdit", MyGUI::FloatCoord(0, 0, 1, 1), MyGUI::Align::Stretch); mLogView->setEditReadOnly(true); MyGUI::TabItem* itemLuaProfiler = mTabControl->addItem("Lua Profiler"); - itemLuaProfiler->setCaptionWithReplacing(" #{DebugMenu:LuaProfiler} "); + itemLuaProfiler->setCaptionWithReplacing(" #{OMWEngine:LuaProfiler} "); mLuaProfiler = itemLuaProfiler->createWidgetReal( "LogEdit", MyGUI::FloatCoord(0, 0, 1, 1), MyGUI::Align::Stretch); mLuaProfiler->setEditReadOnly(true); #ifndef BT_NO_PROFILE MyGUI::TabItem* item = mTabControl->addItem("Physics Profiler"); - item->setCaptionWithReplacing(" #{DebugMenu:PhysicsProfiler} "); + item->setCaptionWithReplacing(" #{OMWEngine:PhysicsProfiler} "); mBulletProfilerEdit = item->createWidgetReal("LogEdit", MyGUI::FloatCoord(0, 0, 1, 1), MyGUI::Align::Stretch); #else diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 21cf30b556..1319379e60 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -558,6 +558,7 @@ namespace mModel->visitQuestNames(!mAllQuests, add); + list->sort(); list->adjustSize(); if (mAllQuests) diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp index 5f58cb3032..9e05c180ca 100644 --- a/apps/openmw/mwgui/postprocessorhud.cpp +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -329,26 +329,22 @@ namespace MWGui case fx::Technique::Status::Uncompiled: { if (technique->getDynamic()) - ss << "#{fontcolourhtml=header}#{PostProcessing:ShaderLocked}: #{fontcolourhtml=normal} " - "#{PostProcessing:ShaderLockedDescription}" + ss << "#{fontcolourhtml=header}#{OMWShaders:ShaderLocked}: #{fontcolourhtml=normal} " + "#{OMWShaders:ShaderLockedDescription}" << endl << endl; - ss << "#{fontcolourhtml=header}#{PostProcessing:Author}: #{fontcolourhtml=normal} " << author + ss << "#{fontcolourhtml=header}#{OMWShaders:Author}: #{fontcolourhtml=normal} " << author << endl + << endl + << "#{fontcolourhtml=header}#{OMWShaders:Version}: #{fontcolourhtml=normal} " << version << endl + << endl + << "#{fontcolourhtml=header}#{OMWShaders:Description}: #{fontcolourhtml=normal} " << description << endl << endl - << "#{fontcolourhtml=header}#{PostProcessing:Version}: #{fontcolourhtml=normal} " << version - << endl - << endl - << "#{fontcolourhtml=header}#{PostProcessing:Description}: #{fontcolourhtml=normal} " << description - << endl - << endl - << "#{fontcolourhtml=header}#{PostProcessing:InInteriors}: #{fontcolourhtml=normal} " - << flag_interior - << "#{fontcolourhtml=header} #{PostProcessing:InExteriors}: #{fontcolourhtml=normal} " - << flag_exterior - << "#{fontcolourhtml=header} #{PostProcessing:Underwater}: #{fontcolourhtml=normal} " + << "#{fontcolourhtml=header}#{OMWShaders:InInteriors}: #{fontcolourhtml=normal} " << flag_interior + << "#{fontcolourhtml=header} #{OMWShaders:InExteriors}: #{fontcolourhtml=normal} " << flag_exterior + << "#{fontcolourhtml=header} #{OMWShaders:Underwater}: #{fontcolourhtml=normal} " << flag_underwater - << "#{fontcolourhtml=header} #{PostProcessing:Abovewater}: #{fontcolourhtml=normal} " + << "#{fontcolourhtml=header} #{OMWShaders:Abovewater}: #{fontcolourhtml=normal} " << flag_abovewater; break; } @@ -370,7 +366,7 @@ namespace MWGui { MyGUI::Button* resetButton = mConfigArea->createWidget("MW_Button", { 0, 0, 0, 24 }, MyGUI::Align::Default); - resetButton->setCaptionWithReplacing("#{PostProcessing:ResetShader}"); + resetButton->setCaptionWithReplacing("#{OMWShaders:ResetShader}"); resetButton->setTextAlign(MyGUI::Align::Center); resetButton->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); resetButton->eventMouseButtonClick diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 462fab75e0..595591a78b 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -111,7 +111,7 @@ namespace MWGui onCharacterSelected(mCharacterSelection, nextCharacter); } else - fillSaveList(); + mCharacterSelection->setIndexSelected(MyGUI::ITEM_NONE); } } @@ -216,7 +216,7 @@ namespace MWGui mCharacterSelection->setIndexSelected(selectedIndex); if (selectedIndex == MyGUI::ITEM_NONE) - mCharacterSelection->setCaptionWithReplacing("#{SavegameMenu:SelectCharacter}"); + mCharacterSelection->setCaptionWithReplacing("#{OMWEngine:SelectCharacter}"); fillSaveList(); } @@ -430,7 +430,7 @@ namespace MWGui if (Settings::Manager::getBool("timeplayed", "Saves")) { text << "\n" - << "#{SavegameMenu:TimePlayed}: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed); + << "#{OMWEngine:TimePlayed}: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed); } mInfoText->setCaptionWithReplacing(text.str()); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index c06eb7ef7e..d7031fffef 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -45,14 +45,14 @@ namespace std::string textureMipmappingToStr(const std::string& val) { if (val == "linear") - return "#{SettingsMenu:TextureFilteringTrilinear}"; + return "#{OMWEngine:TextureFilteringTrilinear}"; if (val == "nearest") - return "#{SettingsMenu:TextureFilteringBilinear}"; + return "#{OMWEngine:TextureFilteringBilinear}"; if (val == "none") - return "#{SettingsMenu:TextureFilteringDisabled}"; + return "#{OMWEngine:TextureFilteringDisabled}"; Log(Debug::Warning) << "Warning: Invalid texture mipmap option: " << val; - return "#{SettingsMenu:TextureFilteringOther}"; + return "#{OMWEngine:TextureFilteringOther}"; } std::string lightingMethodToStr(SceneUtil::LightingMethod method) @@ -61,14 +61,14 @@ namespace switch (method) { case SceneUtil::LightingMethod::FFP: - result = "#{SettingsMenu:LightingMethodLegacy}"; + result = "#{OMWEngine:LightingMethodLegacy}"; break; case SceneUtil::LightingMethod::PerObjectUniform: - result = "#{SettingsMenu:LightingMethodShadersCompatibility}"; + result = "#{OMWEngine:LightingMethodShadersCompatibility}"; break; case SceneUtil::LightingMethod::SingleUBO: default: - result = "#{SettingsMenu:LightingMethodShaders}"; + result = "#{OMWEngine:LightingMethodShaders}"; break; } @@ -533,7 +533,7 @@ namespace MWGui _sender->setCaptionWithReplacing(_sender->getItemNameAt(_sender->getIndexSelected())); MWBase::Environment::get().getWindowManager()->interactiveMessageBox( - "#{SettingsMenu:ChangeRequiresRestart}", { "#{sOK}" }, true); + "#{OMWEngine:ChangeRequiresRestart}", { "#{sOK}" }, true); Settings::Manager::setString("lighting method", "Shaders", *_sender->getItemDataAt(pos)); apply(); @@ -547,7 +547,7 @@ namespace MWGui _sender->setCaptionWithReplacing(_sender->getItemNameAt(_sender->getIndexSelected())); MWBase::Environment::get().getWindowManager()->interactiveMessageBox( - "#{SettingsMenu:ChangeRequiresRestart}", { "#{sOK}" }, true); + "#{OMWEngine:ChangeRequiresRestart}", { "#{sOK}" }, true); std::vector currentLocales = Settings::Manager::getStringArray("preferred locales", "General"); if (currentLocales.size() <= langPriority) @@ -601,7 +601,7 @@ namespace MWGui { std::vector buttons = { "#{sYes}", "#{sNo}" }; MWBase::Environment::get().getWindowManager()->interactiveMessageBox( - "#{SettingsMenu:LightingResetToDefaults}", buttons, true); + "#{OMWEngine:LightingResetToDefaults}", buttons, true); int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); if (selectedButton == 1 || selectedButton == -1) return; diff --git a/apps/openmw/mwinput/actions.hpp b/apps/openmw/mwinput/actions.hpp index e586a613f7..538d12f2c2 100644 --- a/apps/openmw/mwinput/actions.hpp +++ b/apps/openmw/mwinput/actions.hpp @@ -5,75 +5,68 @@ namespace MWInput { enum Actions { - // please add new actions at the bottom, in order to preserve the channel IDs in the key configuration files + // Action IDs are used in the configuration file input_v3.xml - A_GameMenu, + A_GameMenu = 0, - A_Unused, + A_Screenshot = 2, // Take a screenshot - A_Screenshot, // Take a screenshot + A_Inventory = 3, // Toggle inventory screen + A_Console = 4, // Toggle console screen - A_Inventory, // Toggle inventory screen + A_MoveLeft = 5, // Move player left / right + A_MoveRight = 6, + A_MoveForward = 7, // Forward / Backward + A_MoveBackward = 8, - A_Console, // Toggle console screen + A_Activate = 9, - A_MoveLeft, // Move player left / right - A_MoveRight, - A_MoveForward, // Forward / Backward - A_MoveBackward, + A_Use = 10, // Use weapon, spell, etc. + A_Jump = 11, + A_AutoMove = 12, // Toggle Auto-move forward + A_Rest = 13, // Rest + A_Journal = 14, // Journal + A_Run = 17, // Run when held + A_CycleSpellLeft = 18, // cycling through spells + A_CycleSpellRight = 19, + A_CycleWeaponLeft = 20, // Cycling through weapons + A_CycleWeaponRight = 21, + A_AlwaysRun = 23, // Toggle Walking/Running + A_Sneak = 24, - A_Activate, + A_QuickSave = 25, + A_QuickLoad = 26, + A_QuickMenu = 27, + A_ToggleWeapon = 28, + A_ToggleSpell = 29, - A_Use, // Use weapon, spell, etc. - A_Jump, - A_AutoMove, // Toggle Auto-move forward - A_Rest, // Rest - A_Journal, // Journal - A_Weapon, // Draw/Sheath weapon - A_Spell, // Ready/Unready Casting - A_Run, // Run when held - A_CycleSpellLeft, // cycling through spells - A_CycleSpellRight, - A_CycleWeaponLeft, // Cycling through weapons - A_CycleWeaponRight, - A_ToggleSneak, // Toggles Sneak - A_AlwaysRun, // Toggle Walking/Running - A_Sneak, + A_TogglePOV = 30, - A_QuickSave, - A_QuickLoad, - A_QuickMenu, - A_ToggleWeapon, - A_ToggleSpell, + A_QuickKey1 = 31, + A_QuickKey2 = 32, + A_QuickKey3 = 33, + A_QuickKey4 = 34, + A_QuickKey5 = 35, + A_QuickKey6 = 36, + A_QuickKey7 = 37, + A_QuickKey8 = 38, + A_QuickKey9 = 39, + A_QuickKey10 = 40, - A_TogglePOV, + A_QuickKeysMenu = 41, - A_QuickKey1, - A_QuickKey2, - A_QuickKey3, - A_QuickKey4, - A_QuickKey5, - A_QuickKey6, - A_QuickKey7, - A_QuickKey8, - A_QuickKey9, - A_QuickKey10, + A_ToggleHUD = 42, + A_ToggleDebug = 43, - A_QuickKeysMenu, + A_LookUpDown = 44, // Joystick look + A_LookLeftRight = 45, + A_MoveForwardBackward = 46, + A_MoveLeftRight = 47, - A_ToggleHUD, + A_ZoomIn = 48, + A_ZoomOut = 49, - A_ToggleDebug, - - A_LookUpDown, // Joystick look - A_LookLeftRight, - A_MoveForwardBackward, - A_MoveLeftRight, - - A_ZoomIn, - A_ZoomOut, - - A_TogglePostProcessorHUD, + A_TogglePostProcessorHUD = 50, A_Last // Marker for the last item }; diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index a010b4e745..9eac57e830 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -441,13 +441,13 @@ namespace MWInput switch (action) { case A_Screenshot: - return "#{SettingsMenu:Screenshot}"; + return "#{OMWEngine:Screenshot}"; case A_ZoomIn: - return "#{SettingsMenu:CameraZoomIn}"; + return "#{OMWEngine:CameraZoomIn}"; case A_ZoomOut: - return "#{SettingsMenu:CameraZoomOut}"; + return "#{OMWEngine:CameraZoomOut}"; case A_ToggleHUD: - return "#{SettingsMenu:ToggleHUD}"; + return "#{OMWEngine:ToggleHUD}"; case A_Use: return "#{sUse}"; case A_Activate: @@ -519,7 +519,7 @@ namespace MWInput case A_QuickLoad: return "#{sQuickLoadCmd}"; case A_TogglePostProcessorHUD: - return "#{SettingsMenu:TogglePostProcessorHUD}"; + return "#{OMWEngine:TogglePostProcessorHUD}"; default: return {}; // not configurable } diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 0f0062714c..dff4af1afd 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -45,10 +45,23 @@ namespace MWLua cellT["gridX"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridX(); }); cellT["gridY"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridY(); }); cellT["hasWater"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->hasWater(); }); + cellT["hasSky"] = sol::readonly_property([](const CellT& c) { + return c.mStore->getCell()->isExterior() || (c.mStore->getCell()->mData.mFlags & ESM::Cell::QuasiEx) != 0; + }); cellT["isExterior"] = sol::readonly_property([](const CellT& c) { return c.mStore->isExterior(); }); + + // deprecated, use cell:hasTag("QuasiExterior") instead cellT["isQuasiExterior"] = sol::readonly_property( [](const CellT& c) { return (c.mStore->getCell()->mData.mFlags & ESM::Cell::QuasiEx) != 0; }); + cellT["hasTag"] = [](const CellT& c, std::string_view tag) -> bool { + if (tag == "NoSleep") + return (c.mStore->getCell()->mData.mFlags & ESM::Cell::NoSleep) != 0; + else if (tag == "QuasiExterior") + return (c.mStore->getCell()->mData.mFlags & ESM::Cell::QuasiEx) != 0; + return false; + }; + cellT["isInSameSpace"] = [](const CellT& c, const ObjectT& obj) { const MWWorld::Ptr& ptr = obj.ptr(); if (!ptr.isInCell()) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 105a107301..ae3ce82fcb 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -98,14 +98,11 @@ namespace MWLua { "AutoMove", MWInput::A_AutoMove }, { "Rest", MWInput::A_Rest }, { "Journal", MWInput::A_Journal }, - { "Weapon", MWInput::A_Weapon }, - { "Spell", MWInput::A_Spell }, { "Run", MWInput::A_Run }, { "CycleSpellLeft", MWInput::A_CycleSpellLeft }, { "CycleSpellRight", MWInput::A_CycleSpellRight }, { "CycleWeaponLeft", MWInput::A_CycleWeaponLeft }, { "CycleWeaponRight", MWInput::A_CycleWeaponRight }, - { "ToggleSneak", MWInput::A_ToggleSneak }, { "AlwaysRun", MWInput::A_AlwaysRun }, { "Sneak", MWInput::A_Sneak }, diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index c11a87eaac..8516e5e21f 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -401,7 +401,7 @@ namespace MWLua return localScripts->getActorControls(); } - void LuaManager::addCustomLocalScript(const MWWorld::Ptr& ptr, int scriptId) + void LuaManager::addCustomLocalScript(const MWWorld::Ptr& ptr, int scriptId, std::string_view initData) { LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (!localScripts) @@ -411,7 +411,7 @@ namespace MWLua if (ptr.isInCell() && MWBase::Environment::get().getWorldScene()->isCellActive(*ptr.getCell())) mActiveLocalScripts.insert(localScripts); } - localScripts->addCustomScript(scriptId); + localScripts->addCustomScript(scriptId, initData); } LocalScripts* LuaManager::createLocalScripts( diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 9d02d11a3d..6482b7ddeb 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -57,7 +57,7 @@ namespace MWLua void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear". // Used only in Lua bindings - void addCustomLocalScript(const MWWorld::Ptr&, int scriptId); + void addCustomLocalScript(const MWWorld::Ptr&, int scriptId, std::string_view initData); void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } void addInGameConsoleMessage(const std::string& msg, const Misc::Color& color) { diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index ab0e686e86..977aa9569c 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -200,9 +200,8 @@ namespace MWLua if constexpr (std::is_same_v) { // Only for global scripts - objectT["addScript"] = [lua = context.mLua, luaManager = context.mLuaManager]( - const GObject& object, std::string_view path) { - const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); + objectT["addScript"] = [context](const GObject& object, std::string_view path, sol::object initData) { + const LuaUtil::ScriptsConfiguration& cfg = context.mLua->getConfiguration(); std::optional scriptId = cfg.findId(path); if (!scriptId) throw std::runtime_error("Unknown script: " + std::string(path)); @@ -211,7 +210,12 @@ namespace MWLua "Script without CUSTOM tag can not be added dynamically: " + std::string(path)); if (object.ptr().getType() == ESM::REC_STAT) throw std::runtime_error("Attaching scripts to Static is not allowed: " + std::string(path)); - luaManager->addCustomLocalScript(object.ptr(), *scriptId); + if (initData != sol::nil) + context.mLuaManager->addCustomLocalScript(object.ptr(), *scriptId, + LuaUtil::serialize(initData.as(), context.mSerializer)); + else + context.mLuaManager->addCustomLocalScript( + object.ptr(), *scriptId, cfg[*scriptId].mInitializationData); }; objectT["hasScript"] = [lua = context.mLua](const GObject& object, std::string_view path) { const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 21e30ec8ac..9999cfc3c6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -400,6 +400,9 @@ namespace MWMechanics { if (!mAnimation->isPlaying(mCurrentHit)) { + if (isKnockedOut() && mCurrentHit.empty() && knockout) + return; + mHitState = CharState_None; mCurrentHit.clear(); stats.setKnockedDown(false); @@ -450,18 +453,6 @@ namespace MWMechanics mCurrentHit = chooseRandomGroup(hitStateToAnimGroup(CharState_Hit)); } - if (!mAnimation->hasAnimation(mCurrentHit)) - { - // The hit animation is missing. Reset the current hit state and immediately cancel all states as if the - // animation were instantaneous. - mHitState = CharState_None; - mCurrentHit.clear(); - stats.setKnockedDown(false); - stats.setHitRecovery(false); - resetCurrentIdleState(); - return; - } - // Cancel upper body animations if (isKnockedOut() || isKnockedDown()) { @@ -479,6 +470,12 @@ namespace MWMechanics } } + if (!mAnimation->hasAnimation(mCurrentHit)) + { + mCurrentHit.clear(); + return; + } + mAnimation->play( mCurrentHit, priority, MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul); } @@ -755,7 +752,7 @@ namespace MWMechanics // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming // update), the idle animation should be displayed if (((mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped) - || mMovementState != CharState_None || mHitState != CharState_None) + || mMovementState != CharState_None || !mCurrentHit.empty()) && !mPtr.getClass().isBipedal(mPtr)) { resetCurrentIdleState(); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 2140bd5412..4d847a3619 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -643,7 +644,9 @@ namespace MWRender { if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) - || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel)) + || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel) + || (cnode->getName() == "Collada visual scene group" + && dynamic_cast(cnode->getUpdateCallback()))) continue; else refnumSet->mRefnums.push_back(pair.first); diff --git a/apps/openmw/mwrender/precipitationocclusion.cpp b/apps/openmw/mwrender/precipitationocclusion.cpp new file mode 100644 index 0000000000..40eddea270 --- /dev/null +++ b/apps/openmw/mwrender/precipitationocclusion.cpp @@ -0,0 +1,170 @@ +#include "precipitationocclusion.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" + +#include "vismask.hpp" + +namespace +{ + class PrecipitationOcclusionUpdater : public SceneUtil::StateSetUpdater + { + public: + PrecipitationOcclusionUpdater(osg::ref_ptr depthTexture) + : mDepthTexture(depthTexture) + { + } + + private: + void setDefaults(osg::StateSet* stateset) override + { + stateset->setTextureAttributeAndModes(3, mDepthTexture); + stateset->addUniform(new osg::Uniform("orthoDepthMap", 3)); + stateset->addUniform(new osg::Uniform("depthSpaceMatrix", mDepthSpaceMatrix)); + } + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + osg::Camera* camera = nv->asCullVisitor()->getCurrentCamera(); + stateset->getUniform("depthSpaceMatrix")->set(camera->getViewMatrix() * camera->getProjectionMatrix()); + } + + osg::Matrixf mDepthSpaceMatrix; + osg::ref_ptr mDepthTexture; + }; + + class DepthCameraUpdater : public SceneUtil::StateSetUpdater + { + public: + DepthCameraUpdater() + : mDummyTexture(new osg::Texture2D) + { + mDummyTexture->setInternalFormat(GL_RGB); + mDummyTexture->setTextureSize(1, 1); + + Shader::ShaderManager& shaderMgr + = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getShaderManager(); + osg::ref_ptr vertex + = shaderMgr.getShader("precipitationdepth_vertex.glsl", {}, osg::Shader::VERTEX); + osg::ref_ptr fragment + = shaderMgr.getShader("precipitationdepth_fragment.glsl", {}, osg::Shader::FRAGMENT); + mProgram = shaderMgr.getProgram(vertex, fragment); + } + + private: + void setDefaults(osg::StateSet* stateset) override + { + stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf())); + stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->setTextureAttributeAndModes(0, mDummyTexture); + stateset->setRenderBinDetails( + osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); + } + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + osg::Camera* camera = nv->asCullVisitor()->getCurrentCamera(); + stateset->getUniform("projectionMatrix")->set(camera->getProjectionMatrix()); + } + + osg::Matrixf mProjectionMatrix; + osg::ref_ptr mDummyTexture; + osg::ref_ptr mProgram; + }; +} + +namespace MWRender +{ + PrecipitationOccluder::PrecipitationOccluder( + osg::Group* skyNode, osg::Group* sceneNode, osg::Group* rootNode, osg::Camera* camera) + : mSkyNode(skyNode) + , mSceneNode(sceneNode) + , mRootNode(rootNode) + , mSceneCamera(camera) + { + constexpr int rttSize = 256; + + mDepthTexture = new osg::Texture2D; + mDepthTexture->setTextureSize(rttSize, rttSize); + mDepthTexture->setSourceFormat(GL_DEPTH_COMPONENT); + mDepthTexture->setInternalFormat(GL_DEPTH_COMPONENT24); + mDepthTexture->setSourceType(GL_UNSIGNED_INT); + mDepthTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER); + mDepthTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER); + mDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mDepthTexture->setBorderColor( + SceneUtil::AutoDepth::isReversed() ? osg::Vec4(0, 0, 0, 0) : osg::Vec4(1, 1, 1, 1)); + + mCamera = new osg::Camera; + mCamera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); + mCamera->setRenderOrder(osg::Camera::PRE_RENDER); + mCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + mCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + mCamera->setNodeMask(Mask_RenderToTexture); + mCamera->setCullMask(Mask_Scene | Mask_Object | Mask_Static); + mCamera->setViewport(0, 0, rttSize, rttSize); + mCamera->attach(osg::Camera::DEPTH_BUFFER, mDepthTexture); + mCamera->addChild(mSceneNode); + + SceneUtil::setCameraClearDepth(mCamera); + } + + void PrecipitationOccluder::update() + { + const osg::Vec3 pos = mSceneCamera->getInverseViewMatrix().getTrans(); + + const float zmin = pos.z() - mRange.z() - Constants::CellSizeInUnits; + const float zmax = pos.z() + mRange.z() + Constants::CellSizeInUnits; + const float near = 0; + const float far = zmax - zmin; + + const float left = -mRange.x() / 2; + const float right = -left; + const float top = mRange.y() / 2; + const float bottom = -top; + + if (SceneUtil::AutoDepth::isReversed()) + { + mCamera->setProjectionMatrix( + SceneUtil::getReversedZProjectionMatrixAsOrtho(left, right, bottom, top, near, far)); + } + else + { + mCamera->setProjectionMatrix(osg::Matrixf::ortho(left, right, bottom, top, near, far)); + } + + mCamera->setViewMatrixAsLookAt( + osg::Vec3(pos.x(), pos.y(), zmax), osg::Vec3(pos.x(), pos.y(), zmin), osg::Vec3(0, 1, 0)); + } + + void PrecipitationOccluder::enable() + { + mSkyNode->setCullCallback(new PrecipitationOcclusionUpdater(mDepthTexture)); + mCamera->setCullCallback(new DepthCameraUpdater); + + mRootNode->removeChild(mCamera); + mRootNode->addChild(mCamera); + } + + void PrecipitationOccluder::disable() + { + mSkyNode->setCullCallback(nullptr); + mCamera->setCullCallback(nullptr); + + mRootNode->removeChild(mCamera); + } + + void PrecipitationOccluder::updateRange(const osg::Vec3f range) + { + const osg::Vec3f margin = { -50, -50, 0 }; + mRange = range - margin; + } +} diff --git a/apps/openmw/mwrender/precipitationocclusion.hpp b/apps/openmw/mwrender/precipitationocclusion.hpp new file mode 100644 index 0000000000..9910777449 --- /dev/null +++ b/apps/openmw/mwrender/precipitationocclusion.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_MWRENDER_PRECIPITATIONOCCLUSION_H +#define OPENMW_MWRENDER_PRECIPITATIONOCCLUSION_H + +#include +#include + +namespace MWRender +{ + class PrecipitationOccluder + { + public: + PrecipitationOccluder(osg::Group* skyNode, osg::Group* sceneNode, osg::Group* rootNode, osg::Camera* camera); + + void update(); + + void enable(); + + void disable(); + + void updateRange(const osg::Vec3f range); + + private: + osg::Group* mSkyNode; + osg::Group* mSceneNode; + osg::Group* mRootNode; + osg::ref_ptr mCamera; + osg::ref_ptr mSceneCamera; + osg::ref_ptr mDepthTexture; + osg::Vec3f mRange; + }; +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index cb5ad8d9a6..30cf10dada 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -586,8 +586,8 @@ namespace MWRender mFog = std::make_unique(); - mSky = std::make_unique(sceneRoot, resourceSystem->getSceneManager(), mSkyBlending); - mSky->setCamera(mViewer->getCamera()); + mSky = std::make_unique( + sceneRoot, mRootNode, mViewer->getCamera(), resourceSystem->getSceneManager(), mSkyBlending); if (mSkyBlending) { int skyTextureUnit = mResourceSystem->getSceneManager()->getShaderManager().reserveGlobalTextureUnits( @@ -771,9 +771,11 @@ namespace MWRender setAmbientColour(ambient); osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); - mSunLight->setDiffuse(diffuse); - mSunLight->setSpecular(diffuse); - mSunLight->setPosition(osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f)); + setSunColour(diffuse, diffuse, 1.f); + + const osg::Vec4f interiorSunPos = osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f); + mPostProcessor->getStateUpdater()->setSunPos(interiorSunPos, false); + mSunLight->setPosition(interiorSunPos); } void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index af0b161ee1..dd45a8d560 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -228,9 +228,10 @@ namespace namespace MWRender { - SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager, bool enableSkyRTT) + SkyManager::SkyManager(osg::Group* parentNode, osg::Group* rootNode, osg::Camera* camera, + Resource::SceneManager* sceneManager, bool enableSkyRTT) : mSceneManager(sceneManager) - , mCamera(nullptr) + , mCamera(camera) , mAtmosphereNightRoll(0.f) , mCreated(false) , mIsStorm(false) @@ -289,6 +290,8 @@ namespace MWRender mRootNode->setNodeMask(Mask_Sky); mRootNode->addChild(mEarlyRenderBinRoot); mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); + + mPrecipitationOccluder = std::make_unique(skyroot, parentNode, rootNode, camera); } void SkyManager::create() @@ -382,11 +385,6 @@ namespace MWRender mCreated = true; } - void SkyManager::setCamera(osg::Camera* camera) - { - mCamera = camera; - } - void SkyManager::createRain() { if (mRainNode) @@ -466,9 +464,11 @@ namespace MWRender mRainNode->setNodeMask(Mask_WeatherParticles); mRainParticleSystem->setUserValue("simpleLighting", true); + mRainParticleSystem->setUserValue("particleOcclusion", true); mSceneManager->recreateShaders(mRainNode); mRootNode->addChild(mRainNode); + mPrecipitationOccluder->enable(); } void SkyManager::destroyRain() @@ -482,6 +482,7 @@ namespace MWRender mCounter = nullptr; mRainParticleSystem = nullptr; mRainShooter = nullptr; + mPrecipitationOccluder->disable(); } SkyManager::~SkyManager() @@ -563,6 +564,7 @@ namespace MWRender * osg::DegreesToRadians(360.f) / (3600 * 96.f); if (mAtmosphereNightNode->getNodeMask() != 0) mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1))); + mPrecipitationOccluder->update(); } void SkyManager::setEnabled(bool enabled) @@ -606,6 +608,7 @@ namespace MWRender mPlacer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); mCounter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops / mRainEntranceSpeed * 20); + mPrecipitationOccluder->updateRange(rainRange); } } @@ -671,6 +674,10 @@ namespace MWRender mRootNode->removeChild(mParticleNode); mParticleNode = nullptr; } + if (mRainEffect.empty()) + { + mPrecipitationOccluder->disable(); + } } else { @@ -693,6 +700,8 @@ namespace MWRender SceneUtil::FindByClassVisitor findPSVisitor("ParticleSystem"); mParticleEffect->accept(findPSVisitor); + const osg::Vec3 defaultWrapRange = osg::Vec3(1024, 1024, 800); + for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) { osgParticle::ParticleSystem* ps @@ -700,7 +709,7 @@ namespace MWRender osg::ref_ptr program = new osgParticle::ModularProgram; if (!mIsStorm) - program->addOperator(new WrapAroundOperator(mCamera, osg::Vec3(1024, 1024, 800))); + program->addOperator(new WrapAroundOperator(mCamera, defaultWrapRange)); program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false)); program->setParticleSystem(ps); mParticleNode->addChild(program); @@ -713,9 +722,16 @@ namespace MWRender } ps->setUserValue("simpleLighting", true); + ps->setUserValue("particleOcclusion", true); } mSceneManager->recreateShaders(mParticleNode); + + if (mCurrentParticleEffect == "meshes\\snow.nif") + { + mPrecipitationOccluder->enable(); + mPrecipitationOccluder->updateRange(defaultWrapRange); + } } } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index a365d32342..16cc0df7bd 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -8,6 +8,7 @@ #include #include +#include "precipitationocclusion.hpp" #include "skyutil.hpp" namespace osg @@ -43,7 +44,8 @@ namespace MWRender class SkyManager { public: - SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager, bool enableSkyRTT); + SkyManager(osg::Group* parentNode, osg::Group* rootNode, osg::Camera* camera, + Resource::SceneManager* sceneManager, bool enableSkyRTT); ~SkyManager(); void update(float duration); @@ -98,8 +100,6 @@ namespace MWRender void listAssetsToPreload(std::vector& models, std::vector& textures); - void setCamera(osg::Camera* camera); - float getBaseWindSpeed() const; void setSunglare(bool enabled); @@ -151,6 +151,8 @@ namespace MWRender osg::ref_ptr mCounter; osg::ref_ptr mRainShooter; + std::unique_ptr mPrecipitationOccluder; + bool mCreated; bool mIsStorm; diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 3c1b6acc47..e0280c91b5 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -1,9 +1,14 @@ #include "esmloader.hpp" #include "esmstore.hpp" +#include + +#include #include #include +#include #include +#include namespace MWWorld { @@ -21,28 +26,47 @@ namespace MWWorld void EsmLoader::load(const std::filesystem::path& filepath, int& index, Loading::Listener* listener) { - const ESM::ReadersCache::BusyItem reader = mReaders.get(static_cast(index)); - reader->setEncoder(mEncoder); - reader->setIndex(index); - reader->open(filepath); - reader->resolveParentFileIndices(mReaders); + auto stream = Files::openBinaryInputFileStream(filepath); + const ESM::Format format = ESM::readFormat(*stream); + stream->seekg(0); - assert(reader->getGameFiles().size() == reader->getParentFileIndices().size()); - for (std::size_t i = 0, n = reader->getParentFileIndices().size(); i < n; ++i) - if (i == static_cast(reader->getIndex())) - throw std::runtime_error("File " + Files::pathToUnicodeString(reader->getName()) + " asks for parent file " + switch (format) + { + case ESM::Format::Tes3: + { + const ESM::ReadersCache::BusyItem reader = mReaders.get(static_cast(index)); + reader->setEncoder(mEncoder); + reader->setIndex(index); + reader->open(filepath); + reader->resolveParentFileIndices(mReaders); + + assert(reader->getGameFiles().size() == reader->getParentFileIndices().size()); + for (std::size_t i = 0, n = reader->getParentFileIndices().size(); i < n; ++i) + if (i == static_cast(reader->getIndex())) + throw std::runtime_error("File " + Files::pathToUnicodeString(reader->getName()) + " asks for parent file " + reader->getGameFiles()[i].name + ", but it is not available or has been loaded in the wrong order. " "Please run the launcher to fix this issue."); - mESMVersions[index] = reader->getVer(); - mStore.load(*reader, listener, mDialogue); + mESMVersions[index] = reader->getVer(); + mStore.load(*reader, listener, mDialogue); - if (!mMasterFileFormat.has_value() - && (Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".esm") - || Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".omwgame"))) - mMasterFileFormat = reader->getFormat(); + if (!mMasterFileFormat.has_value() + && (Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".esm") + || Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".omwgame"))) + mMasterFileFormat = reader->getFormat(); + break; + } + case ESM::Format::Tes4: + { + ESM4::Reader readerESM4(std::move(stream), filepath); + auto statelessEncoder = mEncoder->getStatelessEncoder(); + readerESM4.setEncoder(&statelessEncoder); + mStore.loadESM4(readerESM4); + break; + } + } } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 03de9fda5d..71f4e1e40d 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -14,6 +14,11 @@ #include #include +#include +#include +#include +#include +#include #include #include "../mwmechanics/spelllist.hpp" @@ -180,6 +185,35 @@ namespace MWWorld } } } + + template + static bool typedReadRecordESM4(ESM4::Reader& reader, Store& store) + { + auto recordType = static_cast(reader.hdr().record.typeId); + + ESM::RecNameInts esm4RecName = static_cast(ESM::esm4Recname(recordType)); + if constexpr (std::is_convertible_v*, DynamicStore*> && HasRecordId::value) + { + if constexpr (ESM::isESM4Rec(T::sRecordId)) + { + if (T::sRecordId == esm4RecName) + { + reader.getRecordData(); + T value; + value.load(reader); + store.insertStatic(value); + return true; + } + } + } + return false; + } + + static bool readRecord(ESM4::Reader& reader, ESMStore& store) + { + return std::apply([&reader](auto&... x) { return (ESMStoreImp::typedReadRecordESM4(reader, x) || ...); }, + store.mStoreImp->mStores); + } }; int ESMStore::find(const ESM::RefId& id) const @@ -338,6 +372,12 @@ namespace MWWorld } } + void ESMStore::loadESM4(ESM4::Reader& reader) + { + auto visitorRec = [this](ESM4::Reader& reader) { return ESMStoreImp::readRecord(reader, *this); }; + ESM4::ReaderUtils::readAll(reader, visitorRec, [](ESM4::Reader&) {}); + } + void ESMStore::setIdType(const ESM::RefId& id, ESM::RecNameInts type) { mStoreImp->mIds[id] = type; diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 7f5773c957..9d737c3f53 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -24,6 +24,14 @@ namespace MWMechanics class SpellList; } +namespace ESM4 +{ + class Reader; + struct Static; + struct Cell; + struct Reference; +} + namespace ESM { class ReadersCache; @@ -78,7 +86,7 @@ namespace MWWorld class ESMStore { friend struct ESMStoreImp; // This allows StoreImp to extend esmstore without beeing included everywhere - + public: using StoreTuple = std::tuple, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, @@ -95,8 +103,11 @@ namespace MWWorld Store, Store, // Special entry which is hardcoded and not loaded from an ESM - Store>; + Store, + Store, Store, Store>; + + private: template static constexpr std::size_t getTypeIndex() { @@ -162,6 +173,7 @@ namespace MWWorld void validateDynamic(); void load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue); + void loadESM4(ESM4::Reader& esm); template const Store& get() const @@ -252,6 +264,16 @@ namespace MWWorld template <> const ESM::NPC* ESMStore::insert(const ESM::NPC& npc); + + template > + struct HasRecordId : std::false_type + { + }; + + template + struct HasRecordId> : std::true_type + { + }; } #endif diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 11e58c0edb..bf8d656ecd 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1,18 +1,19 @@ #include "store.hpp" -#include - -#include -#include -#include - -#include -#include - #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace { // TODO: Switch to C++23 to get a working version of std::unordered_map::erase @@ -161,7 +162,15 @@ namespace MWWorld if (ptr == nullptr) { std::stringstream msg; - msg << T::getRecordType() << " '" << id << "' not found"; + if constexpr (!ESM::isESM4Rec(T::sRecordId)) + { + msg << T::getRecordType(); + } + else + { + msg << "ESM::REC_" << getRecNameString(T::sRecordId).toStringView(); + } + msg << " '" << id << "' not found"; throw std::runtime_error(msg.str()); } return ptr; @@ -171,8 +180,10 @@ namespace MWWorld { T record; bool isDeleted = false; - - record.load(esm, isDeleted); + if constexpr (!ESM::isESM4Rec(T::sRecordId)) + { + record.load(esm, isDeleted); + } std::pair inserted = mStatic.insert_or_assign(record.mId, record); if (inserted.second) @@ -292,9 +303,12 @@ namespace MWWorld { for (typename Dynamic::const_iterator iter(mDynamic.begin()); iter != mDynamic.end(); ++iter) { - writer.startRecord(T::sRecordId); - iter->second.save(writer); - writer.endRecord(T::sRecordId); + if constexpr (!ESM::isESM4Rec(T::sRecordId)) + { + writer.startRecord(T::sRecordId); + iter->second.save(writer); + writer.endRecord(T::sRecordId); + } } } template @@ -302,8 +316,10 @@ namespace MWWorld { T record; bool isDeleted = false; - - record.load(reader, isDeleted); + if constexpr (!ESM::isESM4Rec(T::sRecordId)) + { + record.load(reader, isDeleted); + } insert(record, overrideOnly); return RecordId(record.mId, isDeleted); @@ -1152,6 +1168,20 @@ namespace MWWorld return mKeywordSearch; } + + ESM::FixedString<6> getRecNameString(ESM::RecNameInts recName) + { + ESM::FixedString<6> name; + name.assign(""); + ESM::NAME fourCCName(recName & ~ESM::sEsm4RecnameFlag); + for (int i = 0; i < 4; i++) + name.mData[i] = fourCCName.mData[i]; + if (ESM::isESM4Rec(recName)) + { + name.mData[4] = '4'; + } + return name; + } } template class MWWorld::TypedDynamicStore; @@ -1196,3 +1226,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; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index a6f869e362..6acfb6d41e 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -517,6 +517,8 @@ namespace MWWorld const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() const; }; + ESM::FixedString<6> getRecNameString(ESM::RecNameInts recName); + } // end namespace #endif diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp index 078cb8609e..76ed346daa 100644 --- a/apps/openmw_test_suite/esm/test_fixed_string.cpp +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -2,142 +2,187 @@ #include "components/esm/esmcommon.hpp" #include -TEST(EsmFixedString, operator__eq_ne) +namespace { + TEST(EsmFixedString, operator__eq_ne) { - SCOPED_TRACE("asdc == asdc"); - constexpr ESM::NAME name("asdc"); - char s[4] = { 'a', 's', 'd', 'c' }; - std::string ss(s, 4); + { + SCOPED_TRACE("asdc == asdc"); + constexpr ESM::NAME name("asdc"); + char s[4] = { 'a', 's', 'd', 'c' }; + std::string ss(s, 4); - EXPECT_TRUE(name == s); - EXPECT_TRUE(name == ss.c_str()); - EXPECT_TRUE(name == ss); + EXPECT_TRUE(name == s); + EXPECT_TRUE(name == ss.c_str()); + EXPECT_TRUE(name == ss); + } + { + SCOPED_TRACE("asdc == asdcx"); + constexpr ESM::NAME name("asdc"); + char s[5] = { 'a', 's', 'd', 'c', 'x' }; + std::string ss(s, 5); + + EXPECT_TRUE(name != s); + EXPECT_TRUE(name != ss.c_str()); + EXPECT_TRUE(name != ss); + } + { + SCOPED_TRACE("asdc == asdc[NULL]"); + const ESM::NAME name("asdc"); + char s[5] = { 'a', 's', 'd', 'c', '\0' }; + std::string ss(s, 5); + + EXPECT_TRUE(name == s); + EXPECT_TRUE(name == ss.c_str()); + EXPECT_TRUE(name == ss); + } } - { - SCOPED_TRACE("asdc == asdcx"); - constexpr ESM::NAME name("asdc"); - char s[5] = { 'a', 's', 'd', 'c', 'x' }; - std::string ss(s, 5); - EXPECT_TRUE(name != s); - EXPECT_TRUE(name != ss.c_str()); - EXPECT_TRUE(name != ss); + TEST(EsmFixedString, operator__eq_ne_const) + { + { + SCOPED_TRACE("asdc == asdc (const)"); + constexpr ESM::NAME name("asdc"); + const char s[4] = { 'a', 's', 'd', 'c' }; + std::string ss(s, 4); + + EXPECT_TRUE(name == s); + EXPECT_TRUE(name == ss.c_str()); + EXPECT_TRUE(name == ss); + } + { + SCOPED_TRACE("asdc == asdcx (const)"); + constexpr ESM::NAME name("asdc"); + const char s[5] = { 'a', 's', 'd', 'c', 'x' }; + std::string ss(s, 5); + + EXPECT_TRUE(name != s); + EXPECT_TRUE(name != ss.c_str()); + EXPECT_TRUE(name != ss); + } + { + SCOPED_TRACE("asdc == asdc[NULL] (const)"); + constexpr ESM::NAME name("asdc"); + const char s[5] = { 'a', 's', 'd', 'c', '\0' }; + std::string ss(s, 5); + + EXPECT_TRUE(name == s); + EXPECT_TRUE(name == ss.c_str()); + EXPECT_TRUE(name == ss); + } } - { - SCOPED_TRACE("asdc == asdc[NULL]"); - const ESM::NAME name("asdc"); - char s[5] = { 'a', 's', 'd', 'c', '\0' }; - std::string ss(s, 5); - EXPECT_TRUE(name == s); - EXPECT_TRUE(name == ss.c_str()); - EXPECT_TRUE(name == ss); + TEST(EsmFixedString, empty_strings) + { + { + SCOPED_TRACE("4 bytes"); + ESM::NAME empty = ESM::NAME(); + EXPECT_TRUE(empty == ""); + EXPECT_TRUE(empty == static_cast(0)); + EXPECT_TRUE(empty != "some string"); + EXPECT_TRUE(empty != static_cast(42)); + } + { + SCOPED_TRACE("32 bytes"); + ESM::NAME32 empty = ESM::NAME32(); + EXPECT_TRUE(empty == ""); + EXPECT_TRUE(empty != "some string"); + } + } + + TEST(EsmFixedString, assign_should_zero_untouched_bytes_for_4) + { + ESM::NAME value; + value = static_cast(0xFFFFFFFFu); + value.assign(std::string(1, 'a')); + EXPECT_EQ(value, static_cast('a')) << value.toInt(); + } + + TEST(EsmFixedString, assign_should_only_truncate_for_4) + { + ESM::NAME value; + value.assign(std::string(5, 'a')); + EXPECT_EQ(value, std::string(4, 'a')); + } + + TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero) + { + ESM::FixedString<17> value; + value.assign(std::string(20, 'a')); + EXPECT_EQ(value, std::string(16, 'a')); + } + + TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero_for_32) + { + ESM::NAME32 value; + value.assign(std::string(33, 'a')); + EXPECT_EQ(value, std::string(31, 'a')); + } + + TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero_for_64) + { + ESM::NAME64 value; + value.assign(std::string(65, 'a')); + EXPECT_EQ(value, std::string(63, 'a')); + } + + TEST(EsmFixedString, assignment_operator_is_supported_for_uint32) + { + ESM::NAME value; + value = static_cast(0xFEDCBA98u); + EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); + } + + TEST(EsmFixedString, construction_from_uint32_is_supported) + { + constexpr ESM::NAME value(0xFEDCBA98u); + EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); + } + + TEST(EsmFixedString, construction_from_RecNameInts_is_supported) + { + constexpr ESM::NAME value(ESM::RecNameInts::REC_ACTI); + EXPECT_EQ(value, static_cast(ESM::RecNameInts::REC_ACTI)) << value.toInt(); + } + + TEST(EsmFixedString, equality_operator_for_not_convertible_to_uint32_with_string_literal) + { + const ESM::FixedString<5> value("abcd"); + EXPECT_EQ(value, "abcd"); + } + + TEST(EsmFixedString, equality_operator_for_not_convertible_to_uint32_with_fixed_size_char_array) + { + const ESM::FixedString<5> value("abcd"); + const char other[5] = { 'a', 'b', 'c', 'd', '\0' }; + EXPECT_EQ(value, other); + } + + TEST(EsmFixedString, equality_operator_for_not_convertible_to_uint32_with_const_char_pointer) + { + const ESM::FixedString<5> value("abcd"); + const char other[5] = { 'a', 'b', 'c', 'd', '\0' }; + EXPECT_EQ(value, static_cast(other)); + } + + TEST(EsmFixedString, equality_operator_for_not_convertible_to_uint32_with_string) + { + const ESM::FixedString<5> value("abcd"); + EXPECT_EQ(value, std::string("abcd")); + } + + TEST(EsmFixedString, equality_operator_for_not_convertible_to_uint32_with_string_view) + { + const ESM::FixedString<5> value("abcd"); + const std::string other("abcd"); + EXPECT_EQ(value, std::string_view(other)); + } + + TEST(EsmFixedString, equality_operator_should_not_get_out_of_bounds) + { + ESM::FixedString<5> value; + const char other[5] = { 'a', 'b', 'c', 'd', 'e' }; + std::memcpy(value.mData, other, sizeof(other)); + EXPECT_EQ(value, static_cast(other)); } } -TEST(EsmFixedString, operator__eq_ne_const) -{ - { - SCOPED_TRACE("asdc == asdc (const)"); - constexpr ESM::NAME name("asdc"); - const char s[4] = { 'a', 's', 'd', 'c' }; - std::string ss(s, 4); - - EXPECT_TRUE(name == s); - EXPECT_TRUE(name == ss.c_str()); - EXPECT_TRUE(name == ss); - } - { - SCOPED_TRACE("asdc == asdcx (const)"); - constexpr ESM::NAME name("asdc"); - const char s[5] = { 'a', 's', 'd', 'c', 'x' }; - std::string ss(s, 5); - - EXPECT_TRUE(name != s); - EXPECT_TRUE(name != ss.c_str()); - EXPECT_TRUE(name != ss); - } - { - SCOPED_TRACE("asdc == asdc[NULL] (const)"); - constexpr ESM::NAME name("asdc"); - const char s[5] = { 'a', 's', 'd', 'c', '\0' }; - std::string ss(s, 5); - - EXPECT_TRUE(name == s); - EXPECT_TRUE(name == ss.c_str()); - EXPECT_TRUE(name == ss); - } -} - -TEST(EsmFixedString, empty_strings) -{ - { - SCOPED_TRACE("4 bytes"); - ESM::NAME empty = ESM::NAME(); - EXPECT_TRUE(empty == ""); - EXPECT_TRUE(empty == static_cast(0)); - EXPECT_TRUE(empty != "some string"); - EXPECT_TRUE(empty != static_cast(42)); - } - { - SCOPED_TRACE("32 bytes"); - ESM::NAME32 empty = ESM::NAME32(); - EXPECT_TRUE(empty == ""); - EXPECT_TRUE(empty != "some string"); - } -} - -TEST(EsmFixedString, assign_should_zero_untouched_bytes_for_4) -{ - ESM::NAME value; - value = static_cast(0xFFFFFFFFu); - value.assign(std::string(1, 'a')); - EXPECT_EQ(value, static_cast('a')) << value.toInt(); -} - -TEST(EsmFixedString, assign_should_only_truncate_for_4) -{ - ESM::NAME value; - value.assign(std::string(5, 'a')); - EXPECT_EQ(value, std::string(4, 'a')); -} - -TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero) -{ - ESM::FixedString<17> value; - value.assign(std::string(20, 'a')); - EXPECT_EQ(value, std::string(16, 'a')); -} - -TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero_for_32) -{ - ESM::NAME32 value; - value.assign(std::string(33, 'a')); - EXPECT_EQ(value, std::string(31, 'a')); -} - -TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero_for_64) -{ - ESM::NAME64 value; - value.assign(std::string(65, 'a')); - EXPECT_EQ(value, std::string(63, 'a')); -} - -TEST(EsmFixedString, assignment_operator_is_supported_for_uint32) -{ - ESM::NAME value; - value = static_cast(0xFEDCBA98u); - EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); -} - -TEST(EsmFixedString, construction_from_uint32_is_supported) -{ - constexpr ESM::NAME value(0xFEDCBA98u); - EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); -} - -TEST(EsmFixedString, construction_from_RecNameInts_is_supported) -{ - constexpr ESM::NAME value(ESM::RecNameInts::REC_ACTI); - EXPECT_EQ(value, static_cast(ESM::RecNameInts::REC_ACTI)) << value.toInt(); -} diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index f869f0e448..d2163520f7 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -8,6 +8,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -289,6 +295,41 @@ TEST_F(StoreTest, delete_test) ASSERT_TRUE(mEsmStore.get().getSize() == 1); } +template +static unsigned int hasSameRecordId(const MWWorld::Store& store, ESM::RecNameInts RecName) +{ + if constexpr (MWWorld::HasRecordId::value) + { + return T::sRecordId == RecName ? 1 : 0; + } + else + { + return 0; + } +} + +template +static void testRecNameIntCount(const MWWorld::Store& store, const MWWorld::ESMStore::StoreTuple& stores) +{ + if constexpr (MWWorld::HasRecordId::value) + { + const unsigned int recordIdCount + = std::apply([](auto&&... x) { return (hasSameRecordId(x, T::sRecordId) + ...); }, stores); + ASSERT_EQ(recordIdCount, static_cast(1)) + << "The same RecNameInt is used twice ESM::REC_" << MWWorld::getRecNameString(T::sRecordId).toStringView(); + } +} + +static void testAllRecNameIntUnique(const MWWorld::ESMStore::StoreTuple& stores) +{ + std::apply([&stores](auto&&... x) { (testRecNameIntCount(x, stores), ...); }, stores); +} + +TEST_F(StoreTest, eachRecordTypeShouldHaveUniqueRecordId) +{ + testAllRecNameIntUnique(MWWorld::ESMStore::StoreTuple()); +} + /// Tests overwriting of records. TEST_F(StoreTest, overwrite_test) { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 243ea5841c..c1dbe36798 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -193,6 +193,7 @@ add_component_dir (esm4 reader reference script + readerutils ) add_component_dir (misc diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 7df9d5d59b..ce6df7f1a4 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -276,7 +276,7 @@ namespace DetourNavigator const Loading::ScopedLoad load(listener); if (listener != nullptr) { - listener->setLabel("#{Navigation:BuildingNavigationMesh}"); + listener->setLabel("#{OMWEngine:BuildingNavigationMesh}"); listener->setProgressRange(maxProgress); } while (!mDone.wait_for(lock, std::chrono::milliseconds(20), isDone)) diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index f3e1ad640e..431b85b9ae 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -333,6 +333,11 @@ namespace ESM REC_MSET4 = esm4Recname(ESM4::REC_MSET) // Media Set }; + constexpr bool isESM4Rec(RecNameInts RecName) + { + return RecName & sEsm4RecnameFlag; + } + /// Common subrecords enum SubRecNameInts { diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index f272fa553e..e12a4703ce 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -121,23 +121,23 @@ namespace ESM } }; - template >> - inline bool operator==(const FixedString& lhs, const T* const& rhs) noexcept + template + inline bool operator==(const FixedString& lhs, std::string_view rhs) noexcept { - for (std::size_t i = 0; i < capacity; ++i) + for (std::size_t i = 0, n = std::min(rhs.size(), capacity); i < n; ++i) { if (lhs.mData[i] != rhs[i]) return false; if (lhs.mData[i] == '\0') return true; } - return rhs[capacity] == '\0'; + return rhs.size() <= capacity || rhs[capacity] == '\0'; } - template - inline bool operator==(const FixedString& lhs, const std::string& rhs) noexcept + template >> + inline bool operator==(const FixedString& lhs, const T* const& rhs) noexcept { - return lhs == rhs.c_str(); + return lhs == std::string_view(rhs, capacity); } template @@ -156,6 +156,12 @@ namespace ESM return lhs.toInt() == rhs.toInt(); } + template >> + inline bool operator==(const FixedString<4>& lhs, const T* const& rhs) noexcept + { + return lhs == std::string_view(rhs, 5); + } + template inline bool operator!=(const FixedString& lhs, const Rhs& rhs) noexcept { diff --git a/components/esm/refid.cpp b/components/esm/refid.cpp index 08199df93d..854d403bd5 100644 --- a/components/esm/refid.cpp +++ b/components/esm/refid.cpp @@ -29,6 +29,11 @@ namespace ESM return newRefId; } + RefId RefId::formIdRefId(const ESM4::FormId id) + { + return ESM::RefId::stringRefId(ESM4::formIdToString(id)); + } + bool RefId::operator==(std::string_view rhs) const { return Misc::StringUtils::ciEqual(mId, rhs); diff --git a/components/esm/refid.hpp b/components/esm/refid.hpp index cd3bef27dd..c93ff4df02 100644 --- a/components/esm/refid.hpp +++ b/components/esm/refid.hpp @@ -6,6 +6,8 @@ #include #include +#include + namespace ESM { // RefId is used to represent an Id that identifies an ESM record. These Ids can then be used in @@ -27,6 +29,7 @@ namespace ESM // RefIds that are as string in the code. For serialization, and display. Using explicit conversions make it // very clear where in the code we need to convert from string to RefId and Vice versa. static RefId stringRefId(std::string_view id); + static RefId formIdRefId(const ESM4::FormId id); const std::string& getRefIdString() const { return mId; } private: diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp index 919c81b8e9..0b829d657f 100644 --- a/components/esm4/loadcell.cpp +++ b/components/esm4/loadcell.cpp @@ -32,12 +32,13 @@ #include #include // FLT_MAX for gcc +#include // FIXME: debug only #include -#include // FIXME: debug only - #include "reader.hpp" -//#include "writer.hpp" +// #include "writer.hpp" + +#include // TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent) // @@ -48,8 +49,9 @@ // longer/shorter/same as loading the subrecords. void ESM4::Cell::load(ESM4::Reader& reader) { - mFormId = reader.hdr().record.id; - reader.adjustFormId(mFormId); + auto formId = reader.hdr().record.id; + reader.adjustFormId(formId); + mId = ESM::RefId::formIdRefId(formId); mFlags = reader.hdr().record.flags; mParent = reader.currWorld(); @@ -71,7 +73,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) // WARN: we need to call setCurrCell (and maybe setCurrCellGrid?) again before loading // cell child groups if we are loading them after restoring the context // (may be easier to update the context before saving?) - reader.setCurrCell(mFormId); // save for LAND (and other children) to access later + reader.setCurrCell(formId); // save for LAND (and other children) to access later std::uint32_t esmVer = reader.esmVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; diff --git a/components/esm4/loadcell.hpp b/components/esm4/loadcell.hpp index ee77bd2bfc..49e5d048b0 100644 --- a/components/esm4/loadcell.hpp +++ b/components/esm4/loadcell.hpp @@ -34,6 +34,9 @@ #include "formid.hpp" #include "lighting.hpp" +#include +#include + namespace ESM4 { class Reader; @@ -61,7 +64,7 @@ namespace ESM4 { FormId mParent; // world formId (for grouping cells), from the loading sequence - FormId mFormId; // from the header + ESM::RefId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; @@ -95,6 +98,8 @@ namespace ESM4 // void save(ESM4::Writer& writer) const; void blank(); + + static constexpr ESM::RecNameInts sRecordId = ESM::REC_CELL4; }; } diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp index d96f52a741..b95f24e73a 100644 --- a/components/esm4/loadrefr.cpp +++ b/components/esm4/loadrefr.cpp @@ -30,14 +30,15 @@ #include #include "reader.hpp" -//#include "writer.hpp" +// #include "writer.hpp" void ESM4::Reference::load(ESM4::Reader& reader) { - mFormId = reader.hdr().record.id; - reader.adjustFormId(mFormId); + auto formId = reader.hdr().record.id; + reader.adjustFormId(formId); + mId = ESM::RefId::formIdRefId(formId); mFlags = reader.hdr().record.flags; - mParent = reader.currCell(); // NOTE: only for persistent refs? + mParent = ESM::RefId::formIdRefId(reader.currCell()); // NOTE: only for persistent refs? // TODO: Let the engine apply this? Saved games? // mInitiallyDisabled = ((mFlags & ESM4::Rec_Disabled) != 0) ? true : false; @@ -60,7 +61,9 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; case ESM4::SUB_NAME: { - reader.getFormId(mBaseObj); + FormId BaseId; + reader.getFormId(BaseId); + mBaseObj = ESM::RefId::formIdRefId(BaseId); #if 0 if (mFlags & ESM4::Rec_Disabled) std::cout << "REFR disable at start " << formIdToString(mFormId) << diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp index 04d537932e..fb54c35863 100644 --- a/components/esm4/loadrefr.hpp +++ b/components/esm4/loadrefr.hpp @@ -31,6 +31,9 @@ #include "reference.hpp" // FormId, Placement, EnableParent +#include +#include + namespace ESM4 { class Reader; @@ -71,15 +74,15 @@ namespace ESM4 struct Reference { - FormId mParent; // cell FormId (currently persistent refs only), from the loading sequence - // NOTE: for exterior cells it will be the dummy cell FormId + ESM::RefId mParent; // cell FormId (currently persistent refs only), from the loading sequence + // NOTE: for exterior cells it will be the dummy cell FormId - FormId mFormId; // from the header + ESM::RefId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; - FormId mBaseObj; + ESM::RefId mBaseObj; Placement mPlacement; float mScale = 1.0f; @@ -110,6 +113,8 @@ namespace ESM4 // void save(ESM4::Writer& writer) const; void blank(); + + static constexpr ESM::RecNameInts sRecordId = ESM::REC_REFR4; }; } diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp index e8e789cb1b..edfbf0e4e6 100644 --- a/components/esm4/loadstat.cpp +++ b/components/esm4/loadstat.cpp @@ -30,12 +30,13 @@ #include #include "reader.hpp" -//#include "writer.hpp" +// #include "writer.hpp" void ESM4::Static::load(ESM4::Reader& reader) { - mFormId = reader.hdr().record.id; - reader.adjustFormId(mFormId); + FormId formId = reader.hdr().record.id; + reader.adjustFormId(formId); + mId = ESM::RefId::formIdRefId(formId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) diff --git a/components/esm4/loadstat.hpp b/components/esm4/loadstat.hpp index f34ba6a1cf..3b8a78d8fb 100644 --- a/components/esm4/loadstat.hpp +++ b/components/esm4/loadstat.hpp @@ -33,6 +33,9 @@ #include "formid.hpp" +#include +#include + namespace ESM4 { class Reader; @@ -40,7 +43,7 @@ namespace ESM4 struct Static { - FormId mFormId; // from the header + ESM::RefId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; @@ -53,6 +56,8 @@ namespace ESM4 // void save(ESM4::Writer& writer) const; // void blank(); + + static constexpr ESM::RecNameInts sRecordId = ESM::REC_STAT4; }; } diff --git a/components/esm4/readerutils.hpp b/components/esm4/readerutils.hpp new file mode 100644 index 0000000000..dd202a5f71 --- /dev/null +++ b/components/esm4/readerutils.hpp @@ -0,0 +1,84 @@ +#ifndef OPENMW_COMPONENTS_ESM4_READERUTILS +#define OPENMW_COMPONENTS_ESM4_READERUTILS + +#include +namespace ESM4 +{ + struct ReaderUtils + { + + /* RecordInvocable must be an invocable, takes an ESM4::Reader& as input, and outputs a boolean that indicates + if the record was read or ignored. Will be invoked for every record + + GroupInvocable's invocable must take a ESM4::Reader& as input, doesn't need to output anything. Will be invoked + for every group*/ + template + static void readAll(ESM4::Reader& reader, RecordInvocable&& recordInvocable, GroupInvocable&& groupInvocable) + { + while (reader.hasMoreRecs()) + { + reader.exitGroupCheck(); + if (!readItem(reader, recordInvocable, groupInvocable)) + break; + } + } + + template + static void readRecord(ESM4::Reader& reader, RecordInvocable&& recordInvocable) + { + if (!recordInvocable(reader)) + reader.skipRecordData(); + } + + template + static bool readGroup(ESM4::Reader& reader, RecordInvocable&& recordInvocable, GroupInvocable&& groupInvocable) + { + const ESM4::RecordHeader& header = reader.hdr(); + + groupInvocable(reader); + + switch (static_cast(header.group.type)) + { + case ESM4::Grp_RecordType: + case ESM4::Grp_InteriorCell: + case ESM4::Grp_InteriorSubCell: + case ESM4::Grp_ExteriorCell: + case ESM4::Grp_ExteriorSubCell: + reader.enterGroup(); + return readItem(reader, recordInvocable, groupInvocable); + case ESM4::Grp_WorldChild: + case ESM4::Grp_CellChild: + case ESM4::Grp_TopicChild: + case ESM4::Grp_CellPersistentChild: + case ESM4::Grp_CellTemporaryChild: + case ESM4::Grp_CellVisibleDistChild: + reader.adjustGRUPFormId(); + reader.enterGroup(); + if (!reader.hasMoreRecs()) + return false; + return readItem(reader, recordInvocable, groupInvocable); + } + + reader.skipGroup(); + + return true; + } + + template + static bool readItem(ESM4::Reader& reader, RecordInvocable&& recordInvocable, GroupInvocable&& groupInvocable) + { + if (!reader.getRecordHeader() || !reader.hasMoreRecs()) + return false; + + const ESM4::RecordHeader& header = reader.hdr(); + + if (header.record.typeId == ESM4::REC_GRUP) + return readGroup(reader, recordInvocable, groupInvocable); + + readRecord(reader, recordInvocable); + return true; + } + }; +} + +#endif // !OPENMW_COMPONENTS_ESM4_READERUTILS diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 6b6151b293..6f18727019 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -35,14 +35,13 @@ namespace LuaUtil mAPI.emplace(std::move(packageName), makeReadOnly(std::move(package))); } - bool ScriptsContainer::addCustomScript(int scriptId) + bool ScriptsContainer::addCustomScript(int scriptId, std::string_view initData) { - const ScriptsConfiguration& conf = mLua.getConfiguration(); - assert(conf.isCustomScript(scriptId)); + assert(mLua.getConfiguration().isCustomScript(scriptId)); std::optional onInit, onLoad; bool ok = addScript(scriptId, onInit, onLoad); if (ok && onInit) - callOnInit(scriptId, *onInit, conf[scriptId].mInitializationData); + callOnInit(scriptId, *onInit, initData); return ok; } diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index ebc7fd6d55..0177281be1 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -91,7 +91,7 @@ namespace LuaUtil // new script, adds it to the container, and calls onInit for this script. Returns `true` if the script was // successfully added. The script should have CUSTOM flag. If the flag is not set, or file not found, or has // syntax errors, returns false. If such script already exists in the container, then also returns false. - bool addCustomScript(int scriptId); + bool addCustomScript(int scriptId, std::string_view initData = ""); bool hasScript(int scriptId) const { return mScripts.count(scriptId) != 0; } void removeScript(int scriptId); diff --git a/components/myguiplatform/myguiloglistener.cpp b/components/myguiplatform/myguiloglistener.cpp index 9002d390f5..42dc9c0415 100644 --- a/components/myguiplatform/myguiloglistener.cpp +++ b/components/myguiplatform/myguiloglistener.cpp @@ -37,4 +37,17 @@ namespace osgMyGUI << separator << _line << std::endl; } } + + MyGUI::LogLevel LogFacility::getCurrentLogLevel() const + { + switch (Debug::CurrentDebugLevel) + { + case Debug::Error: + return MyGUI::LogLevel::Error; + case Debug::Warning: + return MyGUI::LogLevel::Warning; + default: + return MyGUI::LogLevel::Info; + } + } } diff --git a/components/myguiplatform/myguiloglistener.hpp b/components/myguiplatform/myguiloglistener.hpp index ec8bd16578..1af63a397f 100644 --- a/components/myguiplatform/myguiloglistener.hpp +++ b/components/myguiplatform/myguiloglistener.hpp @@ -47,12 +47,14 @@ namespace osgMyGUI MyGUI::LevelLogFilter mFilter; MyGUI::LogSource mSource; + MyGUI::LogLevel getCurrentLogLevel() const; + public: LogFacility(const std::filesystem::path& output, bool console) : mFile(output) { mConsole.setEnabled(console); - mFilter.setLoggingLevel(MyGUI::LogLevel::Info); + mFilter.setLoggingLevel(getCurrentLogLevel()); mSource.addLogListener(&mFile); mSource.addLogListener(&mConsole); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 06fbd8470f..b6a22c08a4 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -636,6 +636,9 @@ namespace Resource backToOriginTrans->addChild(newRiggeometryHolder); group->addChild(backToOriginTrans); + + node->getOrCreateUserDataContainer()->addUserObject( + new TemplateRef(newRiggeometryHolder->getGeometry(0))); } } } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 4e5f68edf5..2a2429ff79 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -675,6 +676,11 @@ namespace Shader if (simpleLighting || dynamic_cast(&node)) defineMap["forcePPL"] = "0"; + bool particleOcclusion = false; + node.getUserValue("particleOcclusion", particleOcclusion); + defineMap["particleOcclusion"] + = particleOcclusion && Settings::Manager::getBool("weather particle occlusion", "Shaders") ? "1" : "0"; + if (reqs.mAlphaBlend && mSupportsNormalsRT) { if (reqs.mSoftParticles) diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index df5462dbb9..11a466e44c 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -68,6 +68,8 @@ namespace ToUTF8 /// ASCII-only string. Otherwise returns a view to the input. std::string_view getLegacyEnc(std::string_view input); + StatelessUtf8Encoder getStatelessEncoder() const { return mImpl; } + private: std::string mBuffer; StatelessUtf8Encoder mImpl; diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index 92cf237875..de1c160288 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -4,6 +4,8 @@ #include #include +#include + namespace Gui { @@ -124,6 +126,12 @@ namespace Gui return mItems[at]; } + void MWList::sort() + { + // A special case for separators is not needed for now + std::sort(mItems.begin(), mItems.end(), Misc::StringUtils::ciLess); + } + void MWList::removeItem(const std::string& name) { assert(std::find(mItems.begin(), mItems.end(), name) != mItems.end()); diff --git a/components/widgets/list.hpp b/components/widgets/list.hpp index a0df792696..b9e397f1a8 100644 --- a/components/widgets/list.hpp +++ b/components/widgets/list.hpp @@ -35,6 +35,7 @@ namespace Gui */ void adjustSize(); + void sort(); void addItem(std::string_view name); void addSeparator(); ///< add a seperator between the current and the next item. void removeItem(const std::string& name); diff --git a/docs/README.md b/docs/README.md index f98cf56054..cc81a7b8cb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -49,8 +49,8 @@ pip3 install -r docs/requirements.txt sudo apt install luarocks git clone https://gitlab.com/ptmikheev/openmw-luadocumentor.git cd openmw-luadocumentor/luarocks -luarocks --local pack openmwluadocumentor-0.1.1-1.rockspec -luarocks --local install openmwluadocumentor-0.1.1-1.src.rock +luarocks --local pack openmwluadocumentor-0.2.0-1.rockspec +luarocks --local install openmwluadocumentor-0.2.0-1.src.rock ``` **Windows** @@ -61,8 +61,8 @@ luarocks --local install openmwluadocumentor-0.1.1-1.src.rock - `cd openmw-luadocumentor/luarocks` - open "Developer Command Prompt for VS <2017/2019>" in this directory and run: ```bash -luarocks --local pack openmwluadocumentor-0.1.1-1.rockspec -luarocks --local install openmwluadocumentor-0.1.1-1.src.rock +luarocks --local pack openmwluadocumentor-0.2.0-1.rockspec +luarocks --local install openmwluadocumentor-0.2.0-1.src.rock ``` ### Generating HTML diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index e59b926d0f..061f2ce5c7 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -59,7 +59,8 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid .. include:: tables/aux_packages.rst -**Interfaces of built-in scripts** +Interfaces of built-in scripts +------------------------------ .. list-table:: :widths: 20 20 60 diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 47008428ab..f09b56a47c 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -85,7 +85,7 @@ Let's write a simple example of a `Player script`: .. code-block:: Lua - -- Save to my_lua_mod/example/player.lua + -- Save to my_lua_mod/scripts/example/player.lua local ui = require('openmw.ui') @@ -107,7 +107,7 @@ The options are: 1. Create text file "my_lua_mod.omwscripts" with the following line: :: - PLAYER: example/player.lua + PLAYER: scripts/example/player.lua 2. (not implemented yet) Add the script in OpenMW CS on "Lua scripts" view and save as "my_lua_mod.omwaddon". @@ -122,6 +122,19 @@ Enable it in ``openmw.cfg`` the same way as any other mod: Now every time the player presses "X" on a keyboard, a message is shown. +Lua scripts naming policy +========================= + +Technically scripts can be placed anywhere in the virtual file system, but we recommend to follow the naming policy and choose one of: + +- ``scripts//.lua``: general case. +- ``scripts///.lua``: if "ModName" is short and can potentially collide with other mods. +- ``scripts/.lua``: if it is a simple mod that consists from a single script, the script can placed to ``scripts/`` without subdirs. + +``scripts/omw/`` is reserved for built-in scripts, don't use it in mods. Overriding built-in scripts is not recommended, prefer to adjust their behaviour via :ref:`Interfaces of built-in scripts` instead. + +See also naming policy of :ref:`Localisation Files`. + Format of ``.omwscripts`` ========================= @@ -129,20 +142,20 @@ Format of ``.omwscripts`` # Lines starting with '#' are comments - GLOBAL: my_mod/some_global_script.lua + GLOBAL: scripts/my_mod/some_global_script.lua # Script that will be automatically attached to the player - PLAYER: my_mod/player.lua + PLAYER: scripts/my_mod/player.lua # Local script that will be automatically attached to every NPC and every creature in the game - NPC, CREATURE: my_mod/some_other_script.lua + NPC, CREATURE: scripts/my_mod/some_other_script.lua # Local script that can be attached to any object by a global script - CUSTOM: my_mod/something.lua + CUSTOM: scripts/my_mod/something.lua # Local script that will be automatically attached to any Container AND can be # attached to any other object by a global script. - CONTAINER, CUSTOM: my_mod/container.lua + CONTAINER, CUSTOM: scripts/my_mod/container.lua Each script is described by one line: ``: ``. diff --git a/docs/source/reference/modding/localisation.rst b/docs/source/reference/modding/localisation.rst index bc66053d58..457d2a6c27 100644 --- a/docs/source/reference/modding/localisation.rst +++ b/docs/source/reference/modding/localisation.rst @@ -31,11 +31,26 @@ E.g. if you include ``en_US.yaml`` and ``en_GB.yaml`` localisation files, you sh Note that because of the fallbacks only messages which differ between variants need to be included in the country-specific localisation files. Localisation Files --------------------------- +------------------ Localisation files (containing the message names and translations) should be stored in the VFS as files of the form ``l10n//.yaml``. +**Naming policy** + +"ContextName" should be in line with :ref:`Lua scripts naming policy`: + +- L10n files for ``scripts//.lua`` should be placed to ``l10n//.yaml``. +- L10n files for ``scripts///.lua`` should be placed to ``l10n//.yaml``. + +In most cases one mod should have only one l10n context. Don't create a new context for each single message. Really big mods with hundreds and thousands of messages can have several l10n contexts. In this case all context names should start with the name of the mod. I.e. `` = `` (or ````). + +L10n contexts with prefix "OMW" are reserved for the OpenMW itself (in particular for built-in scripts ``scripts/omw/``) and shouldn't be used in mods. + +Built-in l10n contexts "Interface" and "Calendar" don't have the "OMW" prefix because these messages are more generic and can be reused in mods. + +**Format** + Messages contents have the form of ICU MessageFormat strings. See `the Formatting Messages chapter of the ICU Guide `_ for a guide to MessageFormat, and see diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index bd74d01fdd..e46e9ebb99 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -287,3 +287,18 @@ the look of some particle systems. Note that the rendering will act as if you have 'force shaders' option enabled. This means that shaders will be used to render all objects and the terrain. + +weather particle occlusion +-------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enables particle occlusion for rain and snow particle effects. +When enabled, rain and snow will not clip through ceilings and overhangs. +Currently this relies on an additional render pass, which may lead to a performance hit. + +.. warning:: + This is an experimental feature that may cause visual oddities, especially when using default rain settings. + It is recommended to at least double the rain diameter through `openmw.cfg`.` diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index a4df9aa966..d95e2297dc 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -20,53 +20,43 @@ set(BUILTIN_DATA_FILES fonts/MysticCards.omwfont fonts/MysticCardsFontLicense.txt - l10n/BuiltInShaders/de.yaml - l10n/BuiltInShaders/en.yaml - l10n/BuiltInShaders/ru.yaml - l10n/BuiltInShaders/sv.yaml - l10n/BuiltInShaders/fr.yaml + # Month names and date formatting l10n/Calendar/de.yaml l10n/Calendar/en.yaml l10n/Calendar/ru.yaml l10n/Calendar/sv.yaml l10n/Calendar/fr.yaml - l10n/DebugMenu/de.yaml - l10n/DebugMenu/en.yaml - l10n/DebugMenu/ru.yaml - l10n/DebugMenu/sv.yaml - l10n/DebugMenu/fr.yaml + + # Generic UI messages that can be reused by mods l10n/Interface/de.yaml l10n/Interface/en.yaml l10n/Interface/ru.yaml l10n/Interface/sv.yaml l10n/Interface/fr.yaml - l10n/Navigation/de.yaml - l10n/Navigation/en.yaml - l10n/Navigation/ru.yaml - l10n/Navigation/sv.yaml - l10n/Navigation/fr.yaml + + # L10n for scripts/omw l10n/OMWCamera/de.yaml l10n/OMWCamera/en.yaml l10n/OMWCamera/ru.yaml l10n/OMWCamera/sv.yaml l10n/OMWCamera/fr.yaml l10n/OMWControls/en.yaml + l10n/OMWControls/ru.yaml l10n/OMWControls/sv.yaml - l10n/PostProcessing/de.yaml - l10n/PostProcessing/en.yaml - l10n/PostProcessing/ru.yaml - l10n/PostProcessing/sv.yaml - l10n/PostProcessing/fr.yaml - l10n/SavegameMenu/de.yaml - l10n/SavegameMenu/en.yaml - l10n/SavegameMenu/ru.yaml - l10n/SavegameMenu/sv.yaml - l10n/SavegameMenu/fr.yaml - l10n/SettingsMenu/de.yaml - l10n/SettingsMenu/en.yaml - l10n/SettingsMenu/ru.yaml - l10n/SettingsMenu/sv.yaml - l10n/SettingsMenu/fr.yaml + + # L10n for OpenMW menus and non-game-specific messages + l10n/OMWEngine/de.yaml + l10n/OMWEngine/en.yaml + l10n/OMWEngine/ru.yaml + l10n/OMWEngine/sv.yaml + l10n/OMWEngine/fr.yaml + + # L10n for post-processing HUD and built-in shaders + l10n/OMWShaders/de.yaml + l10n/OMWShaders/en.yaml + l10n/OMWShaders/ru.yaml + l10n/OMWShaders/sv.yaml + l10n/OMWShaders/fr.yaml openmw_aux/util.lua openmw_aux/time.lua diff --git a/files/data/l10n/BuiltInShaders/de.yaml b/files/data/l10n/BuiltInShaders/de.yaml deleted file mode 100644 index 5886356782..0000000000 --- a/files/data/l10n/BuiltInShaders/de.yaml +++ /dev/null @@ -1,7 +0,0 @@ -DisplayDepthName: "Visualisiert den Tiefenpuffer." -DisplayDepthFactorDescription: "Bestimmt die Korrelation zwischen dem Pixeltiefenwert und seiner Ausgabefarbe. Hohe Werte führen zu einem helleren Bild." -DisplayDepthFactorName: "Farbfaktor" -ContrastLevelDescription: "Kontraststufe" -ContrastLevelName: "Kontrast" -GammaLevelDescription: "Gamma-Level" -GammaLevelName: "Gamma" \ No newline at end of file diff --git a/files/data/l10n/DebugMenu/de.yaml b/files/data/l10n/DebugMenu/de.yaml deleted file mode 100644 index 2ff4abd89f..0000000000 --- a/files/data/l10n/DebugMenu/de.yaml +++ /dev/null @@ -1,4 +0,0 @@ -DebugWindow: "Debug" -LogViewer: "Protokollansicht" -LuaProfiler: "Lua-Profiler" -PhysicsProfiler: "Physik-Profiler" diff --git a/files/data/l10n/DebugMenu/en.yaml b/files/data/l10n/DebugMenu/en.yaml deleted file mode 100644 index 6f76d147c6..0000000000 --- a/files/data/l10n/DebugMenu/en.yaml +++ /dev/null @@ -1,4 +0,0 @@ -DebugWindow: "Debug" -LogViewer: "Log Viewer" -LuaProfiler: "Lua Profiler" -PhysicsProfiler: "Physics Profiler" diff --git a/files/data/l10n/DebugMenu/fr.yaml b/files/data/l10n/DebugMenu/fr.yaml deleted file mode 100644 index 8b6716e5c6..0000000000 --- a/files/data/l10n/DebugMenu/fr.yaml +++ /dev/null @@ -1,3 +0,0 @@ -DebugWindow: "Fenêtre de débogage" -LogViewer: "Journal" -PhysicsProfiler: "Profileur des performances de la physique" diff --git a/files/data/l10n/DebugMenu/ru.yaml b/files/data/l10n/DebugMenu/ru.yaml deleted file mode 100644 index 0cd7131705..0000000000 --- a/files/data/l10n/DebugMenu/ru.yaml +++ /dev/null @@ -1,4 +0,0 @@ -DebugWindow: "Меню отладки" -LogViewer: "Журнал логов" -LuaProfiler: "Профилировщик Луа" -PhysicsProfiler: "Профилировщик физики" diff --git a/files/data/l10n/DebugMenu/sv.yaml b/files/data/l10n/DebugMenu/sv.yaml deleted file mode 100644 index 07807782de..0000000000 --- a/files/data/l10n/DebugMenu/sv.yaml +++ /dev/null @@ -1,3 +0,0 @@ -DebugWindow: "Felsökning" -LogViewer: "Loggvisare" -PhysicsProfiler: "Fysikprofilerare" diff --git a/files/data/l10n/Navigation/de.yaml b/files/data/l10n/Navigation/de.yaml deleted file mode 100644 index c63a9aeb25..0000000000 --- a/files/data/l10n/Navigation/de.yaml +++ /dev/null @@ -1 +0,0 @@ -BuildingNavigationMesh: "Baue Navigationsgitter" diff --git a/files/data/l10n/Navigation/en.yaml b/files/data/l10n/Navigation/en.yaml deleted file mode 100644 index 06fb8c26ba..0000000000 --- a/files/data/l10n/Navigation/en.yaml +++ /dev/null @@ -1 +0,0 @@ -BuildingNavigationMesh: "Building navigation mesh" diff --git a/files/data/l10n/Navigation/fr.yaml b/files/data/l10n/Navigation/fr.yaml deleted file mode 100644 index e7a6f9aecc..0000000000 --- a/files/data/l10n/Navigation/fr.yaml +++ /dev/null @@ -1 +0,0 @@ -BuildingNavigationMesh: "Construction du mesh de navigation" diff --git a/files/data/l10n/Navigation/ru.yaml b/files/data/l10n/Navigation/ru.yaml deleted file mode 100644 index 509c178dea..0000000000 --- a/files/data/l10n/Navigation/ru.yaml +++ /dev/null @@ -1 +0,0 @@ -BuildingNavigationMesh: "Построение навигационной сетки" diff --git a/files/data/l10n/Navigation/sv.yaml b/files/data/l10n/Navigation/sv.yaml deleted file mode 100644 index d7c4780f94..0000000000 --- a/files/data/l10n/Navigation/sv.yaml +++ /dev/null @@ -1 +0,0 @@ -BuildingNavigationMesh: "Bygger navigeringsmesh" diff --git a/files/data/l10n/OMWControls/ru.yaml b/files/data/l10n/OMWControls/ru.yaml new file mode 100644 index 0000000000..30bb15ee57 --- /dev/null +++ b/files/data/l10n/OMWControls/ru.yaml @@ -0,0 +1,16 @@ +ControlsPage: "Управление OpenMW" +ControlsPageDescription: "Дополнительные настройки, связанные с управлением игроком" + +MovementSettings: "Движение" + +alwaysRun: "Постоянный бег" +alwaysRunDescription: | + Когда эта настройка включена, по умолчанию персонаж бегает, в противном случае по умолчанию он ходит. + Клавиша бега (по умолчанию Shift) инвертирует режим временно, а клавиша постоянного бега (Caps Lock) переключает эту настройку. + +toggleSneak: "Переключение движения крадучись" +toggleSneakDescription: | + Эта настройка меняет поведение клавиши движения крадучись (по умолчанию Ctrl): + чтобы красться, её достаточно нажать единожды для переключения положения, а не зажимать. + Игрокам, которые много времени крадутся, может быть проще управлять персонажем, когда опция включена. + diff --git a/files/data/l10n/SettingsMenu/de.yaml b/files/data/l10n/OMWEngine/de.yaml similarity index 92% rename from files/data/l10n/SettingsMenu/de.yaml rename to files/data/l10n/OMWEngine/de.yaml index 80c02597e2..6b9018b5a6 100644 --- a/files/data/l10n/SettingsMenu/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -1,3 +1,24 @@ +# Debug window + +DebugWindow: "Debug" +LogViewer: "Protokollansicht" +LuaProfiler: "Lua-Profiler" +PhysicsProfiler: "Physik-Profiler" + + +# Messages + +BuildingNavigationMesh: "Baue Navigationsgitter" + + +# Save game menu + +SelectCharacter: "Charakterauswahl..." +TimePlayed: "Spielzeit" + + +# Settings menu + ActorsProcessingRange: "Akteur-Verarbeitungsreichweite" Anisotropy: "Anisotropie" CameraSensitivity: "Kameraempfindlichkeit" diff --git a/files/data/l10n/SettingsMenu/en.yaml b/files/data/l10n/OMWEngine/en.yaml similarity index 90% rename from files/data/l10n/SettingsMenu/en.yaml rename to files/data/l10n/OMWEngine/en.yaml index fea8ae78b7..49725aba2a 100644 --- a/files/data/l10n/SettingsMenu/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -1,3 +1,24 @@ +# Debug window + +DebugWindow: "Debug" +LogViewer: "Log Viewer" +LuaProfiler: "Lua Profiler" +PhysicsProfiler: "Physics Profiler" + + +# Messages + +BuildingNavigationMesh: "Building navigation mesh" + + +# Save game menu + +SelectCharacter: "Select Character..." +TimePlayed: "Time played" + + +# Settings menu + ActorsProcessingRange: "Actors Processing Range" Anisotropy: "Anisotropy" CameraSensitivity: "Camera Sensitivity" @@ -62,6 +83,6 @@ WaterShaderTextureQuality: "Texture quality" WindowBorder: "Window Border" WindowMode: "Window Mode" WindowModeFullscreen: "Fullscreen" -WindowModeHint: "Hint: the Windowed Fullscreen mode\nalways uses a native screen resolution." +WindowModeHint: "Hint: Windowed Fullscreen mode\nalways uses the native display resolution." WindowModeWindowed: "Windowed" WindowModeWindowedFullscreen: "Windowed Fullscreen" diff --git a/files/data/l10n/SettingsMenu/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml similarity index 93% rename from files/data/l10n/SettingsMenu/fr.yaml rename to files/data/l10n/OMWEngine/fr.yaml index 29b1a4ec71..b2f4197a64 100644 --- a/files/data/l10n/SettingsMenu/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -1,3 +1,23 @@ +# Debug window + +DebugWindow: "Fenêtre de débogage" +LogViewer: "Journal" +PhysicsProfiler: "Profileur des performances de la physique" + + +# Messages + +BuildingNavigationMesh: "Construction du mesh de navigation" + + +# Save game menu + +SelectCharacter: "Sélection du personnage..." +TimePlayed: "Temps de jeu" + + +# Settings menu + ActorsProcessingRange: "Distance de traitement pour les personnages" Anisotropy: "Anisotropie" CameraSensitivity: "Sensibilité de la caméra" diff --git a/files/data/l10n/SettingsMenu/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml similarity index 93% rename from files/data/l10n/SettingsMenu/ru.yaml rename to files/data/l10n/OMWEngine/ru.yaml index fc9760c3d6..35beec7f32 100644 --- a/files/data/l10n/SettingsMenu/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -1,3 +1,24 @@ +# Debug window + +DebugWindow: "Меню отладки" +LogViewer: "Журнал логов" +LuaProfiler: "Профилировщик Луа" +PhysicsProfiler: "Профилировщик физики" + + +# Messages + +BuildingNavigationMesh: "Построение навигационной сетки" + + +# Save game menu + +SelectCharacter: "Выберите персонажа..." +TimePlayed: "Время в игре" + + +# Settings menu + ActorsProcessingRange: "Дальность обработки персонажей" Anisotropy: "Анизотропная фильтрация" CameraSensitivity: "Чувствительность камеры" diff --git a/files/data/l10n/SettingsMenu/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml similarity index 93% rename from files/data/l10n/SettingsMenu/sv.yaml rename to files/data/l10n/OMWEngine/sv.yaml index 865582b76f..704d1f719b 100644 --- a/files/data/l10n/SettingsMenu/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -1,3 +1,23 @@ +# Debug window + +DebugWindow: "Felsökning" +LogViewer: "Loggvisare" +PhysicsProfiler: "Fysikprofilerare" + + +# Messages + +BuildingNavigationMesh: "Bygger navigeringsmesh" + + +# Save game menu + +SelectCharacter: "Välj spelfigur..." +TimePlayed: "Speltid" + + +# Settings menu + ActorsProcessingRange: "Processavstånd för figurer" Anisotropy: "Anisotropi" CameraSensitivity: "Kamerakänslighet" diff --git a/files/data/l10n/PostProcessing/de.yaml b/files/data/l10n/OMWShaders/de.yaml similarity index 62% rename from files/data/l10n/PostProcessing/de.yaml rename to files/data/l10n/OMWShaders/de.yaml index fb0846555b..7d17199dd9 100644 --- a/files/data/l10n/PostProcessing/de.yaml +++ b/files/data/l10n/OMWShaders/de.yaml @@ -1,3 +1,5 @@ +# Post-processing HUD + Abovewater: "Überwasser" ActiveShaders: "Aktive Shader" Author: "Autor" @@ -19,3 +21,14 @@ ShaderLockedDescription: "Kann nicht umgeschaltet oder verschoben werden, gesteu ShaderResetUniform: "r" Underwater: "Unterwasser" Version: "Version" + + +# Built-in post-processing shaders + +DisplayDepthName: "Visualisiert den Tiefenpuffer." +DisplayDepthFactorDescription: "Bestimmt die Korrelation zwischen dem Pixeltiefenwert und seiner Ausgabefarbe. Hohe Werte führen zu einem helleren Bild." +DisplayDepthFactorName: "Farbfaktor" +ContrastLevelDescription: "Kontraststufe" +ContrastLevelName: "Kontrast" +GammaLevelDescription: "Gamma-Level" +GammaLevelName: "Gamma" diff --git a/files/data/l10n/BuiltInShaders/en.yaml b/files/data/l10n/OMWShaders/en.yaml similarity index 63% rename from files/data/l10n/BuiltInShaders/en.yaml rename to files/data/l10n/OMWShaders/en.yaml index 00354a45ff..8221be933f 100644 --- a/files/data/l10n/BuiltInShaders/en.yaml +++ b/files/data/l10n/OMWShaders/en.yaml @@ -1,3 +1,30 @@ +# Post-processing HUD + +Abovewater: "Abovewater" +ActiveShaders: "Active Shaders" +Author: "Author" +Description: "Description" +InactiveShaders: "Inactive Shaders" +InExteriors: "Exteriors" +InInteriors: "Interiors" +KeyboardControls: | + Keyboard controls: + + Shift+Right-Arrow > Activate shader + Shift+Left-Arrow > Deactive shader + Shift+Up-Arrow > Move shader up + Shift+Down-Arrow > Move shader down +PostProcessHUD: "Postprocess HUD" +ResetShader: "Reset shader to default state" +ShaderLocked: "Locked" +ShaderLockedDescription: "Cannot be toggled or moved, controlled by external Lua script" +ShaderResetUniform: "r" +Underwater: "Underwater" +Version: "Version" + + +# Built-in post-processing shaders + AdjustmentsDescription: "Colour adjustments." BloomDescription: "Bloom shader performing its calculations in (approximately) linear light." DebugDescription: "Debug shader." diff --git a/files/data/l10n/BuiltInShaders/fr.yaml b/files/data/l10n/OMWShaders/fr.yaml similarity index 64% rename from files/data/l10n/BuiltInShaders/fr.yaml rename to files/data/l10n/OMWShaders/fr.yaml index 8e43bd848a..3b4e47370e 100644 --- a/files/data/l10n/BuiltInShaders/fr.yaml +++ b/files/data/l10n/OMWShaders/fr.yaml @@ -1,3 +1,30 @@ +# Post-processing HUD + +Abovewater: "Hors de l'eau" +ActiveShaders: "Shaders actifs" +Author: "Auteur(e)" +Description: "Description" +InactiveShaders: "Shaders inactifs" +InExteriors: "À l'extérieur" +InInteriors: "En intérieur" +KeyboardControls: | + Raccourcis clavier: + + Majuscule+Flèche droite > Active le shader + Majuscule+Flèche gauche > Désactive le shader + Majuscule+Flèche haut > Monte le shader dans la liste + Majuscule+Flèche bas > Descend le shader dans la liste +MainPassDescription: "Transmet les données de la scène aux shaders de post-traitement. Ne peut être déplacé ou supprimé." +PostProcessHUD: "HUD de post-traitement" +ResetShader: "Restaure le shader dans sa configuration par défaut" +ShaderLocked: "Verrouillé" +ShaderResetUniform: "r" +Underwater: "Sous l'eau" +Version: "Version" + + +# Built-in post-processing shaders + AdjustmentsDescription: "Ajustements de l'image (couleurs, luminosité, contrastes...)." BloomDescription: "Flou lumineux, calculé (approximativement) de façon linéaire suivant l'intensité de la lumière." DebugDescription: "Shader de débogage." diff --git a/files/data/l10n/BuiltInShaders/ru.yaml b/files/data/l10n/OMWShaders/ru.yaml similarity index 62% rename from files/data/l10n/BuiltInShaders/ru.yaml rename to files/data/l10n/OMWShaders/ru.yaml index b61c61c29b..b886f72b54 100644 --- a/files/data/l10n/BuiltInShaders/ru.yaml +++ b/files/data/l10n/OMWShaders/ru.yaml @@ -1,3 +1,30 @@ +# Post-processing HUD + +Abovewater: "Над водой" +ActiveShaders: "Включенные шейдеры" +Author: "Автор" +Description: "Описание" +InactiveShaders: "Выключенные шейдеры" +InExteriors: "Вне помещений" +InInteriors: "В помещениях" +KeyboardControls: | + Управление с помощью клавиатуры: + + Shift+Right-Arrow > Включить шейдер + Shift+Left-Arrow > Выключить шейдер + Shift+Up-Arrow > Передвинуть шейдер выше + Shift+Down-Arrow > Передвинуть шейдер ниже +PostProcessHUD: "Настройки постобработки" +ResetShader: "Обнулить настройки этого шейдера" +ShaderLocked: "Заблокирован" +ShaderLockedDescription: "Не может быть выключен или передвинут, управляется внешним Lua-скриптом" +ShaderResetUniform: "x" +Underwater: "Под водой" +Version: "Версия" + + +# Built-in post-processing shaders + AdjustmentsDescription: "Коррекция цвета." DebugDescription: "Отладочный шейдер." DebugHeaderDepth: "Буфер глубины" diff --git a/files/data/l10n/BuiltInShaders/sv.yaml b/files/data/l10n/OMWShaders/sv.yaml similarity index 59% rename from files/data/l10n/BuiltInShaders/sv.yaml rename to files/data/l10n/OMWShaders/sv.yaml index fe9bfc8350..c1f1a5a333 100644 --- a/files/data/l10n/BuiltInShaders/sv.yaml +++ b/files/data/l10n/OMWShaders/sv.yaml @@ -1,3 +1,30 @@ +# Post-processing HUD + +Abovewater: "Ovan vatten" +ActiveShaders: "Aktiva shaders" +Author: "Skapare" # Author = Författare, but författare sounds very book-author-ish. Skapare, meaning "creator", sounds better in Swedish in this case. Ok? +Description: "Beskrivning" +ActiveShaders: "Inaktiva shaders" +InExteriors: "Exteriörer" +InInteriors: "Interiörer" +KeyboardControls: | + Tangentbordskontroller: + + Shift+Högerpil > Aktivera shader + Shift+Vänsterpil > Avaktivera shader + Shift+Pil upp > Flytta shader upp + Shift+Pil ner > Flytta shader ner +PostProcessHUD: "Postprocess HUD" +ResetShader: "Återställ shader to ursprungligt läge" +ShaderLocked: "Låst" +ShaderLockedDescription: "Kan ej aktiveras/inaktiveras eller flyttas. Kontrolleras av externt Lua-skript" +ShaderResetUniform: "r" +Underwater: "Under vatten" +Version: "Version" + + +# Built-in post-processing shaders + AdjustmentsDescription: "Färgjusteringar." BloomDescription: "Bloomshader som utför sina beräkningar i (ungefärligt) linjärt ljus." DebugDescription: "Felsökningsshader." @@ -10,7 +37,7 @@ DisplayNormalsName: "Visualisera normalvektorer" # på engelska står det "pass ContrastLevelDescription: "Kontrastnivå" ContrastLevelName: "Kontrast" GammaLevelDescription: "Gammanivå" -# GammaLevelName: "Gamma" samma som engelska +GammaLevelName: "Gamma" # samma som engelska StrengthLevelName: "Styrka" StrengthLevelDescription: "Effektens styrka." RadiusLevelName: "Radie" diff --git a/files/data/l10n/PostProcessing/en.yaml b/files/data/l10n/PostProcessing/en.yaml deleted file mode 100644 index 5416cd723f..0000000000 --- a/files/data/l10n/PostProcessing/en.yaml +++ /dev/null @@ -1,21 +0,0 @@ -Abovewater: "Abovewater" -ActiveShaders: "Active Shaders" -Author: "Author" -Description: "Description" -InactiveShaders: "Inactive Shaders" -InExteriors: "Exteriors" -InInteriors: "Interiors" -KeyboardControls: | - Keyboard controls: - - Shift+Right-Arrow > Activate shader - Shift+Left-Arrow > Deactive shader - Shift+Up-Arrow > Move shader up - Shift+Down-Arrow > Move shader down -PostProcessHUD: "Postprocess HUD" -ResetShader: "Reset shader to default state" -ShaderLocked: "Locked" -ShaderLockedDescription: "Cannot be toggled or moved, controlled by external Lua script" -ShaderResetUniform: "r" -Underwater: "Underwater" -Version: "Version" diff --git a/files/data/l10n/PostProcessing/fr.yaml b/files/data/l10n/PostProcessing/fr.yaml deleted file mode 100644 index c5500ac09d..0000000000 --- a/files/data/l10n/PostProcessing/fr.yaml +++ /dev/null @@ -1,21 +0,0 @@ -Abovewater: "Hors de l'eau" -ActiveShaders: "Shaders actifs" -Author: "Auteur(e)" -Description: "Description" -InactiveShaders: "Shaders inactifs" -InExteriors: "À l'extérieur" -InInteriors: "En intérieur" -KeyboardControls: | - Raccourcis clavier: - - Majuscule+Flèche droite > Active le shader - Majuscule+Flèche gauche > Désactive le shader - Majuscule+Flèche haut > Monte le shader dans la liste - Majuscule+Flèche bas > Descend le shader dans la liste -MainPassDescription: "Transmet les données de la scène aux shaders de post-traitement. Ne peut être déplacé ou supprimé." -PostProcessHUD: "HUD de post-traitement" -ResetShader: "Restaure le shader dans sa configuration par défaut" -ShaderLocked: "Verrouillé" -ShaderResetUniform: "r" -Underwater: "Sous l'eau" -Version: "Version" diff --git a/files/data/l10n/PostProcessing/ru.yaml b/files/data/l10n/PostProcessing/ru.yaml deleted file mode 100644 index d0a137bb9a..0000000000 --- a/files/data/l10n/PostProcessing/ru.yaml +++ /dev/null @@ -1,21 +0,0 @@ -Abovewater: "Над водой" -ActiveShaders: "Включенные шейдеры" -Author: "Автор" -Description: "Описание" -InactiveShaders: "Выключенные шейдеры" -InExteriors: "Вне помещений" -InInteriors: "В помещениях" -KeyboardControls: | - Управление с помощью клавиатуры: - - Shift+Right-Arrow > Включить шейдер - Shift+Left-Arrow > Выключить шейдер - Shift+Up-Arrow > Передвинуть шейдер выше - Shift+Down-Arrow > Передвинуть шейдер ниже -PostProcessHUD: "Настройки постобработки" -ResetShader: "Обнулить настройки этого шейдера" -ShaderLocked: "Заблокирован" -ShaderLockedDescription: "Не может быть выключен или передвинут, управляется внешним Lua-скриптом" -ShaderResetUniform: "x" -Underwater: "Под водой" -Version: "Версия" diff --git a/files/data/l10n/PostProcessing/sv.yaml b/files/data/l10n/PostProcessing/sv.yaml deleted file mode 100644 index 5c2e11a011..0000000000 --- a/files/data/l10n/PostProcessing/sv.yaml +++ /dev/null @@ -1,22 +0,0 @@ -Abovewater: "Ovan vatten" -ActiveShaders: "Aktiva shaders" -Author: "Skapare" # Author = Författare, but författare sounds very book-author-ish. Skapare, meaning "creator", sounds better in Swedish in this case. Ok? -Description: "Beskrivning" -ActiveShaders: "Inaktiva shaders" -InExteriors: "Exteriörer" -InInteriors: "Interiörer" -KeyboardControls: | - Tangentbordskontroller: - - Shift+Högerpil > Aktivera shader - Shift+Vänsterpil > Avaktivera shader - Shift+Pil upp > Flytta shader upp - Shift+Pil ner > Flytta shader ner -PostProcessHUD: "Postprocess HUD" -ResetShader: "Återställ shader to ursprungligt läge" -ShaderLocked: "Låst" -ShaderLockedDescription: "Kan ej aktiveras/inaktiveras eller flyttas. Kontrolleras av externt Lua-skript" -ShaderResetUniform: "r" -Underwater: "Under vatten" -Version: "Version" - diff --git a/files/data/l10n/SavegameMenu/de.yaml b/files/data/l10n/SavegameMenu/de.yaml deleted file mode 100644 index 225b6c3a07..0000000000 --- a/files/data/l10n/SavegameMenu/de.yaml +++ /dev/null @@ -1,2 +0,0 @@ -SelectCharacter: "Charakterauswahl..." -TimePlayed: "Spielzeit" diff --git a/files/data/l10n/SavegameMenu/en.yaml b/files/data/l10n/SavegameMenu/en.yaml deleted file mode 100644 index b7e3c60a5f..0000000000 --- a/files/data/l10n/SavegameMenu/en.yaml +++ /dev/null @@ -1,2 +0,0 @@ -SelectCharacter: "Select Character..." -TimePlayed: "Time played" diff --git a/files/data/l10n/SavegameMenu/fr.yaml b/files/data/l10n/SavegameMenu/fr.yaml deleted file mode 100644 index 15bc37b4a7..0000000000 --- a/files/data/l10n/SavegameMenu/fr.yaml +++ /dev/null @@ -1,2 +0,0 @@ -SelectCharacter: "Sélection du personnage..." -TimePlayed: "Temps de jeu" diff --git a/files/data/l10n/SavegameMenu/ru.yaml b/files/data/l10n/SavegameMenu/ru.yaml deleted file mode 100644 index 8b87500bd3..0000000000 --- a/files/data/l10n/SavegameMenu/ru.yaml +++ /dev/null @@ -1,2 +0,0 @@ -SelectCharacter: "Выберите персонажа..." -TimePlayed: "Время в игре" diff --git a/files/data/l10n/SavegameMenu/sv.yaml b/files/data/l10n/SavegameMenu/sv.yaml deleted file mode 100644 index ef23465f6c..0000000000 --- a/files/data/l10n/SavegameMenu/sv.yaml +++ /dev/null @@ -1,2 +0,0 @@ -SelectCharacter: "Välj spelfigur..." -TimePlayed: "Speltid" diff --git a/files/data/mygui/openmw_debug_window.layout b/files/data/mygui/openmw_debug_window.layout index a2635c3c46..7019c5292d 100644 --- a/files/data/mygui/openmw_debug_window.layout +++ b/files/data/mygui/openmw_debug_window.layout @@ -1,7 +1,7 @@ - + diff --git a/files/data/mygui/openmw_postprocessor_hud.layout b/files/data/mygui/openmw_postprocessor_hud.layout index 0c2868435a..4ed8ecdb36 100644 --- a/files/data/mygui/openmw_postprocessor_hud.layout +++ b/files/data/mygui/openmw_postprocessor_hud.layout @@ -2,7 +2,7 @@ - + @@ -12,7 +12,7 @@ - + @@ -34,7 +34,7 @@ - + @@ -80,7 +80,7 @@ - + diff --git a/files/data/mygui/openmw_postprocessor_hud.skin.xml b/files/data/mygui/openmw_postprocessor_hud.skin.xml index b25b7f1372..35ab652f2c 100644 --- a/files/data/mygui/openmw_postprocessor_hud.skin.xml +++ b/files/data/mygui/openmw_postprocessor_hud.skin.xml @@ -59,7 +59,7 @@ - + diff --git a/files/data/mygui/openmw_savegame_dialog.layout b/files/data/mygui/openmw_savegame_dialog.layout index d2d8df295f..a1cdba89ab 100644 --- a/files/data/mygui/openmw_savegame_dialog.layout +++ b/files/data/mygui/openmw_savegame_dialog.layout @@ -6,7 +6,7 @@ - + diff --git a/files/data/mygui/openmw_settings_window.layout b/files/data/mygui/openmw_settings_window.layout index 0569751191..59e9d44ecc 100644 --- a/files/data/mygui/openmw_settings_window.layout +++ b/files/data/mygui/openmw_settings_window.layout @@ -75,7 +75,7 @@ - + @@ -87,7 +87,7 @@ - + @@ -204,10 +204,10 @@ - + - + @@ -226,7 +226,7 @@ - + @@ -238,7 +238,7 @@ - + @@ -277,13 +277,13 @@ - + - - - + + + @@ -293,7 +293,7 @@ - + @@ -303,7 +303,7 @@ - + @@ -313,15 +313,15 @@ - + - + - + @@ -339,7 +339,7 @@ - + @@ -375,17 +375,17 @@ - + - - + + - + @@ -394,7 +394,7 @@ - + @@ -435,7 +435,7 @@ - + @@ -449,7 +449,7 @@ - + @@ -463,14 +463,14 @@ - + - + @@ -489,7 +489,7 @@ - + @@ -498,12 +498,12 @@ - - - - - - + + + + + + @@ -511,7 +511,7 @@ - + @@ -521,9 +521,9 @@ - - - + + + @@ -532,10 +532,10 @@ - + - + @@ -546,7 +546,7 @@ - + @@ -554,7 +554,7 @@ - + @@ -567,7 +567,7 @@ - + @@ -579,13 +579,13 @@ - + - + @@ -597,13 +597,13 @@ - + - + @@ -615,13 +615,13 @@ - + - + @@ -631,7 +631,7 @@ - + @@ -640,10 +640,10 @@ - + - + @@ -681,24 +681,24 @@ - + - + - + - + - + - + diff --git a/files/data/shaders/adjustments.omwfx b/files/data/shaders/adjustments.omwfx index dff182d422..4a9d8ab73b 100644 --- a/files/data/shaders/adjustments.omwfx +++ b/files/data/shaders/adjustments.omwfx @@ -3,8 +3,8 @@ uniform_float uGamma { step = 0.01; min = 0.0; max = 5.0; - display_name = "#{BuiltInShaders:GammaLevelName}"; - description = "#{BuiltInShaders:GammaLevelDescription}"; + display_name = "#{OMWShaders:GammaLevelName}"; + description = "#{OMWShaders:GammaLevelDescription}"; } uniform_float uContrast { @@ -12,8 +12,8 @@ uniform_float uContrast { step = 0.01; min = 0.0; max = 5.0; - display_name = "#{BuiltInShaders:ContrastLevelName}"; - description = "#{BuiltInShaders:ContrastLevelDescription}"; + display_name = "#{OMWShaders:ContrastLevelName}"; + description = "#{OMWShaders:ContrastLevelDescription}"; } fragment main { @@ -31,7 +31,7 @@ fragment main { } technique { - description = "#{BuiltInShaders:AdjustmentsDescription}"; + description = "#{OMWShaders:AdjustmentsDescription}"; version = "1.0"; author = "OpenMW"; passes = main; diff --git a/files/data/shaders/bloomlinear.omwfx b/files/data/shaders/bloomlinear.omwfx index a0b78e4026..3de7139f39 100644 --- a/files/data/shaders/bloomlinear.omwfx +++ b/files/data/shaders/bloomlinear.omwfx @@ -3,48 +3,48 @@ uniform_float uGamma { min = 0.1; max = 4.0; step = 0.01; - display_name = "#{BuiltInShaders:GammaLevelName}"; - description = "#{BuiltInShaders:GammaLevelDescription}"; + display_name = "#{OMWShaders:GammaLevelName}"; + description = "#{OMWShaders:GammaLevelDescription}"; } uniform_float uThreshold { default = 0.35; min = 0.0; max = 1.0; step = 0.01; - display_name = "#{BuiltInShaders:BloomThresholdLevelName}"; - description = "#{BuiltInShaders:BloomThresholdLevelDescription}"; + display_name = "#{OMWShaders:BloomThresholdLevelName}"; + description = "#{OMWShaders:BloomThresholdLevelDescription}"; } uniform_float uClamp { default = 1.0; min = 0.0; max = 1.0; step = 0.01; - display_name = "#{BuiltInShaders:BloomClampLevelName}"; - description = "#{BuiltInShaders:BloomClampLevelDescription}"; + display_name = "#{OMWShaders:BloomClampLevelName}"; + description = "#{OMWShaders:BloomClampLevelDescription}"; } uniform_float uSkyFactor { default = 0.5; min = 0.0; max = 2.0; step = 0.01; - display_name = "#{BuiltInShaders:SkyFactorLevelName}"; - description = "#{BuiltInShaders:SkyFactorLevelDescription}"; + display_name = "#{OMWShaders:SkyFactorLevelName}"; + description = "#{OMWShaders:SkyFactorLevelDescription}"; } uniform_float uRadius { default = 0.5; min = 0.0; max = 1.0; step = 0.01; - display_name = "#{BuiltInShaders:RadiusLevelName}"; - description = "#{BuiltInShaders:RadiusLevelDescription}"; + display_name = "#{OMWShaders:RadiusLevelName}"; + description = "#{OMWShaders:RadiusLevelDescription}"; } uniform_float uStrength { default = 0.25; min = 0.0; max = 1.0; step = 0.01; - display_name = "#{BuiltInShaders:StrengthLevelName}"; - description = "#{BuiltInShaders:StrengthLevelDescription}"; + display_name = "#{OMWShaders:StrengthLevelName}"; + description = "#{OMWShaders:StrengthLevelDescription}"; } shared { @@ -215,7 +215,7 @@ fragment final(rt1=RT_Vertical) { technique { passes = nomipmap, horizontal, vertical, final; - description = "#{BuiltInShaders:BloomDescription}"; + description = "#{OMWShaders:BloomDescription}"; author = "OpenMW"; version = "1.0"; } diff --git a/files/data/shaders/debug.omwfx b/files/data/shaders/debug.omwfx index addcbf6c38..4bfeea2499 100644 --- a/files/data/shaders/debug.omwfx +++ b/files/data/shaders/debug.omwfx @@ -1,7 +1,7 @@ uniform_bool uDisplayDepth { - header = "#{BuiltInShaders:DebugHeaderDepth}"; + header = "#{OMWShaders:DebugHeaderDepth}"; default = true; - display_name = "#{BuiltInShaders:DisplayDepthName}"; + display_name = "#{OMWShaders:DisplayDepthName}"; } uniform_float uDepthFactor { @@ -9,14 +9,14 @@ uniform_float uDepthFactor { min = 0.01; max = 20.0; default = 1.0; - display_name = "#{BuiltInShaders:DisplayDepthFactorName}"; - description = "#{BuiltInShaders:DisplayDepthFactorDescription}"; + display_name = "#{OMWShaders:DisplayDepthFactorName}"; + description = "#{OMWShaders:DisplayDepthFactorDescription}"; } uniform_bool uDisplayNormals { - header = "#{BuiltInShaders:DebugHeaderNormals}"; + header = "#{OMWShaders:DebugHeaderNormals}"; default = true; - display_name = "#{BuiltInShaders:DisplayNormalsName}"; + display_name = "#{OMWShaders:DisplayNormalsName}"; } fragment main { @@ -38,7 +38,7 @@ fragment main { technique { passes = main; - description = "#{BuiltInShaders:DebugDescription}"; + description = "#{OMWShaders:DebugDescription}"; author = "OpenMW"; version = "1.0"; pass_normals = true; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 38f73ec669..659ae9a6e3 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -112,7 +112,7 @@ -- @field #Cell cell The cell where the object currently is. During loading a game and for objects in an inventory or a container `cell` is nil. -- @field #table type Type of the object (one of the tables from the package @{openmw.types#types}). -- @field #number count Count (makes sense if stored in a container). --- @field #string recordId Record ID. +-- @field #string recordId Returns record ID of the object in lowercase. --- -- Does the object still exist and is available. @@ -144,6 +144,7 @@ -- @function [parent=#GameObject] addScript -- @param self -- @param #string scriptPath Path to the script in OpenMW virtual filesystem. +-- @param #table initData (optional) Initialization data to be passed to onInit. If missed then Lua initialization data from content files will be used (if exists for this script). --- -- Whether a script with given path is attached to this object. @@ -182,11 +183,19 @@ -- @type Cell -- @field #string name Name of the cell (can be empty string). -- @field #string region Region of the cell. --- @field #boolean isExterior Whether the cell is an exterior. --- @field #boolean isQuasiExterior Whether the cell is a quasi exterior (like interior but with the sky and the wheather). +-- @field #boolean isExterior Whether the cell is an exterior cell. "Exterior" means grid of cells where the player can seamless walk from one cell to another without teleports. QuasiExterior (interior with sky) is not an exterior. +-- @field #boolean isQuasiExterior (DEPRECATED, use `hasTag("QuasiExterior")`) Whether the cell is a quasi exterior (like interior but with the sky and the wheather). -- @field #number gridX Index of the cell by X (only for exteriors). -- @field #number gridY Index of the cell by Y (only for exteriors). -- @field #boolean hasWater True if the cell contains water. +-- @field #boolean hasSky True if in this cell sky should be rendered. + +--- +-- Returns true if the cell has given tag. +-- @function [parent=#Cell] hasTag +-- @param self +-- @param #string tag One of "QuasiExterior", "NoSleep". +-- @return #boolean --- -- Returns true either if the cell contains the object or if the cell is an exterior and the object is also in an exterior. diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 205ab584d9..e0aa917420 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -118,14 +118,11 @@ -- @field [parent=#ACTION] #number Jump -- @field [parent=#ACTION] #number AutoMove -- @field [parent=#ACTION] #number Journal --- @field [parent=#ACTION] #number Weapon --- @field [parent=#ACTION] #number Spell -- @field [parent=#ACTION] #number Run -- @field [parent=#ACTION] #number CycleSpellLeft -- @field [parent=#ACTION] #number CycleSpellRight -- @field [parent=#ACTION] #number CycleWeaponLeft -- @field [parent=#ACTION] #number CycleWeaponRight --- @field [parent=#ACTION] #number ToggleSneak -- @field [parent=#ACTION] #number AlwaysRun -- @field [parent=#ACTION] #number Sneak -- @field [parent=#ACTION] #number QuickSave diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 7c6c2e62fb..0981ecacc0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -499,6 +499,9 @@ adjust coverage for alpha test = true # Soften intersection of blended particle systems with opaque geometry soft particles = false +# Rain and snow particle occlusion +weather particle occlusion = false + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index d688f9e7d9..9215f78da0 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -34,8 +34,8 @@ set(SHADER_FILES shadowcasting_vertex.glsl shadowcasting_fragment.glsl vertexcolors.glsl - multiview_resolve_vertex.glsl - multiview_resolve_fragment.glsl + multiview_resolve_vertex.glsl + multiview_resolve_fragment.glsl nv_default_vertex.glsl nv_default_fragment.glsl nv_nolighting_vertex.glsl @@ -55,6 +55,8 @@ set(SHADER_FILES fullscreen_tri_vertex.glsl fullscreen_tri_fragment.glsl fog.glsl + precipitation_vertex.glsl + precipitation_fragment.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index caf8b672ca..f05ff8eba8 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -92,8 +92,30 @@ varying vec3 passNormal; #include "softparticles.glsl" #endif +#if @particleOcclusion +uniform sampler2D orthoDepthMap; + +varying vec3 orthoDepthMapCoord; + +void precipitationOcclusion() +{ + float sceneDepth = texture2D(orthoDepthMap, orthoDepthMapCoord.xy * 0.5 + 0.5).r; +#if @reverseZ + if (orthoDepthMapCoord.z < sceneDepth) + discard; +#else + if (orthoDepthMapCoord.z * 0.5 + 0.5 > sceneDepth) + discard; +#endif +} +#endif + void main() { +#if @particleOcclusion + precipitationOcclusion(); +#endif + #if @diffuseMap vec2 adjustedDiffuseUV = diffuseMapUV; #endif diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 971b95d8e0..12af0fa003 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -66,8 +66,20 @@ varying vec3 passNormal; #include "lighting.glsl" #include "depth.glsl" +#if @particleOcclusion +varying vec3 orthoDepthMapCoord; + +uniform mat4 depthSpaceMatrix; +uniform mat4 osg_ViewMatrixInverse; +#endif + void main(void) { +#if @particleOcclusion + mat4 model = osg_ViewMatrixInverse * gl_ModelViewMatrix; + orthoDepthMapCoord = ((depthSpaceMatrix * model) * vec4(gl_Vertex.xyz, 1.0)).xyz; +#endif + gl_Position = mw_modelToClip(gl_Vertex); vec4 viewPos = mw_modelToView(gl_Vertex); diff --git a/files/shaders/precipitation_fragment.glsl b/files/shaders/precipitation_fragment.glsl new file mode 100644 index 0000000000..b16eb0fd99 --- /dev/null +++ b/files/shaders/precipitation_fragment.glsl @@ -0,0 +1,15 @@ +#version 120 + +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; + +#include "vertexcolors.glsl" + +void main() +{ + gl_FragData[0].rgb = vec3(1.0); + gl_FragData[0].a = texture2D(diffuseMap, diffuseMapUV).a * getDiffuseColor().a; + + if (gl_FragData[0].a <= 0.5) + discard; +} \ No newline at end of file diff --git a/files/shaders/precipitation_vertex.glsl b/files/shaders/precipitation_vertex.glsl new file mode 100644 index 0000000000..a202d9cd96 --- /dev/null +++ b/files/shaders/precipitation_vertex.glsl @@ -0,0 +1,13 @@ +#version 120 + +uniform mat4 projectionMatrix; +varying vec2 diffuseMapUV; + +#include "vertexcolors.glsl" + +void main() +{ + gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + diffuseMapUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy; + passColor = gl_Color; +} \ No newline at end of file diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index d1da187fbe..f373f5532a 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -485,6 +485,16 @@ + + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + + + Weather Particle Occlusion + + +